-
Notifications
You must be signed in to change notification settings - Fork 134
fix: [#1009] activate Snowflake Cortex prompt caching for Claude models #1020
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
40234c7
04b0faf
39597d7
10c67e5
1912781
96280da
84bc9fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,8 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { Hooks, PluginInput } from "@opencode-ai/plugin" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Auth, OAUTH_DUMMY_KEY } from "@/auth" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Log } from "@/altimate/util/log" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const log = Log.create({ service: "snowflake-cortex" }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Build the set of Snowflake Cortex model IDs that support tool calling. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -40,6 +43,137 @@ export function buildToolCapableSet( | |||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Snowflake account identifiers contain only alphanumeric, hyphen, underscore, and dot characters. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const VALID_ACCOUNT_RE = /^[a-zA-Z0-9._-]+$/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Snowflake Cortex accepts at most 4 cache breakpoints per request (Anthropic limit). */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const CACHE_BREAKPOINT_LIMIT = 4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * How long to stop injecting cache markers after Cortex rejects a cache-marked | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * request. Matches Cortex's 5-minute ephemeral cache TTL: by the time the | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * cooldown expires the cache would be cold anyway, so re-trying markers costs | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * almost nothing — while a false trip (transient 400 whose stripped retry | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * happened to succeed) self-heals instead of silently billing the whole | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * session at the uncached rate. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const CACHE_DISABLE_COOLDOWN_MS = 5 * 60 * 1000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // `any` is deliberate: these guards navigate arbitrary request-body JSON whose | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // message/block properties are read and mutated without a fixed schema. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| function isRecord(value: unknown): value is Record<string, any> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return typeof value === "object" && value !== null && !Array.isArray(value) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Roles whose content-block cache markers Cortex honors (verified live 2026-07-20). */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const CACHEABLE_ROLES = new Set(["system", "user", "assistant"]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Attach a cache marker to the last non-empty content block of a message. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * String content is wrapped into a text block. Returns false when the message | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * has no block that can carry a marker (e.g. empty content). | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| function attachMarker(msg: Record<string, any>, marker: Record<string, any>): boolean { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof msg.content === "string") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (msg.content.length === 0) return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| msg.content = [{ type: "text", text: msg.content, cache_control: marker }] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!Array.isArray(msg.content)) return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = msg.content.length - 1; i >= 0; i--) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const block = msg.content[i] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isRecord(block)) continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (block.type === "text" && (typeof block.text !== "string" || block.text.length === 0)) continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isRecord(block.cache_control)) block.cache_control = marker | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Relocate prompt-cache markers into content blocks for Claude models. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Cortex only honors caching via `messages[].content[].cache_control` | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * (ephemeral, max 4 breakpoints) — and only on system/user/assistant messages; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * markers on `role:"tool"` messages are accepted but silently ignored | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * (verified against a live Cortex account). The OpenAI-compatible AI SDK | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * serializes our cache providerOptions as message-level fields — on system | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * messages, on single-text-part user messages (collapsed to string content), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * on tool messages, and into assistant `tool_calls` entries — all of which | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Cortex ignores, so every request bills the full input rate. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Message-level markers move into that message's content blocks; a marker on | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * a tool message (or a message with no attachable block) walks back to the | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * nearest earlier cacheable message, keeping the breakpoint as close to the | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * end of the conversation as Cortex allows. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Returns true when the request carries block-level markers after relocation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function relocateCacheControl(parsed: Record<string, any>): boolean { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Every Claude model Cortex serves uses a `claude-` prefixed ID; a prefix | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // match avoids false positives on user-registered aliases that merely | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // contain the substring. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof parsed.model !== "string" || !parsed.model.startsWith("claude-")) return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!Array.isArray(parsed.messages)) return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const messages = parsed.messages | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let idx = 0; idx < messages.length; idx++) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const msg = messages[idx] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isRecord(msg)) continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The SDK spreads part metadata into tool_calls entries — invalid location. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Array.isArray(msg.tool_calls)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const call of msg.tool_calls) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isRecord(call)) delete call.cache_control | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const marker = isRecord(msg.cache_control) ? msg.cache_control : undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| delete msg.cache_control | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!marker) continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let t = idx; t >= 0; t--) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const target = messages[t] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isRecord(target) || !CACHEABLE_ROLES.has(target.role)) continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (attachMarker(target, marker)) break | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Enforce the breakpoint limit, keeping the last markers (longest prefixes). | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Markers on non-cacheable roles are dead weight against the limit — drop them. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const marked: Record<string, any>[] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const msg of messages) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isRecord(msg) || !Array.isArray(msg.content)) continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const block of msg.content) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isRecord(block) || !block.cache_control) continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!CACHEABLE_ROLES.has(msg.role)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| delete block.cache_control | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| marked.push(block) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const block of marked.slice(0, Math.max(0, marked.length - CACHE_BREAKPOINT_LIMIT))) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| delete block.cache_control | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return marked.length > 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| function stripParsedCacheControl(parsed: Record<string, any>) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!Array.isArray(parsed.messages)) return | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const msg of parsed.messages) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isRecord(msg)) continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| delete msg.cache_control | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Array.isArray(msg.tool_calls)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const call of msg.tool_calls) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isRecord(call)) delete call.cache_control | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!Array.isArray(msg.content)) continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const block of msg.content) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isRecord(block)) delete block.cache_control | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Parse a `account::token` PAT credential string. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function parseSnowflakePAT(code: string): { account: string; token: string } | null { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sep = code.indexOf("::") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -53,16 +187,23 @@ export function parseSnowflakePAT(code: string): { account: string; token: strin | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Transform a Snowflake Cortex request body string. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Returns a Response to short-circuit the fetch (synthetic stop), or undefined to continue normally. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param toolCapable Model IDs that should retain `tools` / `tool_choice` / tool messages. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Build via `buildToolCapableSet(provider.models)` at loader time so | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * user-added models with `tool_call: true` in `altimate-code.json` are honored. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param cacheControl When false, strip every cache marker instead of relocating | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * them into content blocks (set after Cortex rejected a | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * cache-marked request). | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns `body` — the rewritten request body; `syntheticStop` — a Response that | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * short-circuits the fetch entirely (trailing-assistant continuation); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * `cacheApplied` — true when the body carries block-level cache markers. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @throws SyntaxError when `bodyText` is not valid JSON. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function transformSnowflakeBody( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bodyText: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| toolCapable: ReadonlySet<string>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): { body: string; syntheticStop?: Response } { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheControl = true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): { body: string; syntheticStop?: Response; cacheApplied?: boolean } { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parsed = JSON.parse(bodyText) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Snowflake uses max_completion_tokens instead of max_tokens | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -71,6 +212,15 @@ export function transformSnowflakeBody( | |||||||||||||||||||||||||||||||||||||||||||||||||||
| delete parsed.max_tokens | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| let cacheApplied = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (cacheControl) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheApplied = relocateCacheControl(parsed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (typeof parsed.model === "string" && parsed.model.startsWith("claude-")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Disabled mode only unwinds what relocation would have produced — keep | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // non-Claude payloads untouched (Cortex ignores their markers anyway). | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| stripParsedCacheControl(parsed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Strip tools for models that don't support tool calling on Snowflake Cortex. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Also remove orphaned tool_calls from messages to avoid Snowflake API errors. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!toolCapable.has(parsed.model)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -105,6 +255,7 @@ export function transformSnowflakeBody( | |||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify(parsed), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheApplied, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| syntheticStop: new Response(stream, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: 200, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { "content-type": "text/event-stream", "cache-control": "no-cache" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -113,7 +264,7 @@ export function transformSnowflakeBody( | |||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { body: JSON.stringify(parsed) } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { body: JSON.stringify(parsed), cacheApplied } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function SnowflakeCortexAuthPlugin(_input: PluginInput): Promise<Hooks> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -137,6 +288,11 @@ export async function SnowflakeCortexAuthPlugin(_input: PluginInput): Promise<Ho | |||||||||||||||||||||||||||||||||||||||||||||||||||
| // request time because a static hardcoded set never sees user additions. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const toolCapable = buildToolCapableSet(provider.models) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Cortex documents block-level cache_control for Claude models, but not | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // for every message role. If an account rejects a cache-marked request, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // fall back once and pause marker injection for a cooldown period. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let cacheDisabledUntil = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| apiKey: OAUTH_DUMMY_KEY, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fetch(requestInput: RequestInfo | URL, init?: RequestInit) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -162,29 +318,74 @@ export async function SnowflakeCortexAuthPlugin(_input: PluginInput): Promise<Ho | |||||||||||||||||||||||||||||||||||||||||||||||||||
| headers.set("X-Snowflake-Authorization-Token-Type", "PROGRAMMATIC_ACCESS_TOKEN") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| let body = init?.body | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let text = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let cacheApplied = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (body) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let text: string | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof body === "string") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| text = body | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (body instanceof Uint8Array || body instanceof ArrayBuffer) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| text = new TextDecoder().decode(body) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ReadableStream, Blob, FormData — pass through untransformed | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| text = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof body === "string") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| text = body | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (body instanceof Uint8Array || body instanceof ArrayBuffer) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| text = new TextDecoder().decode(body) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ReadableStream, Blob, FormData — pass through untransformed | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (text) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let result | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = transformSnowflakeBody(text, toolCapable, Date.now() >= cacheDisabledUntil) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Non-JSON body — pass through untransformed. Anything else | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // is a transform bug and must surface, not degrade silently. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!(error instanceof SyntaxError)) throw error | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (text) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = transformSnowflakeBody(text, toolCapable) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (result) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (result.syntheticStop) return result.syntheticStop | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| body = result.body | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheApplied = result.cacheApplied === true | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers.delete("content-length") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // JSON parse error — pass original body through untransformed | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fetch(requestInput, { ...init, headers, body }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(requestInput, { ...init, headers, body }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If Cortex rejects a cache-marked request, retry once without the | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // markers; when the stripped retry succeeds, the markers were the | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // problem — pause marker injection for a cooldown period. The retry | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // fires on any 400 (Cortex's marker-rejection error text is | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // undocumented, so there is no reliable signal to scope on); a | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // deterministic non-marker 400 recurs identically on the retry and | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // never trips the cooldown. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (response.status === 400 && cacheApplied) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const stripped = transformSnowflakeBody(text, toolCapable, false).body | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (stripped !== body) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Pause markers eagerly so concurrent in-flight requests stop | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // sending the rejected shape while this probe retry runs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // restored below if the retry shows markers weren't the cause. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const previousDisabledUntil = cacheDisabledUntil | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheDisabledUntil = Date.now() + CACHE_DISABLE_COOLDOWN_MS | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Concurrent 400 probes can clear the cooldown after another probe succeeds, so subsequent requests resume sending the rejected cache-marker shape. Associate restoration with this probe's deadline (or serialize probes) so a stale failed retry cannot overwrite a newer in-flight pause. Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| let retry: Response | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| retry = await fetch(requestInput, { ...init, headers, body: stripped }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Transport failure on the probe — surface the original 400. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheDisabledUntil = previousDisabledUntil | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When two cache-marked requests probe concurrently, one stripped retry can succeed and establish a confirmed cooldown after the other request captured Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (retry.ok) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.warn("Cortex rejected cache-marked request; pausing prompt caching", { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: response.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cooldownMs: CACHE_DISABLE_COOLDOWN_MS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| void response.body?.cancel().catch(() => {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return retry | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Retry failed too — the 400 wasn't the markers; resume injection. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheDisabledUntil = previousDisabledUntil | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| void retry.body?.cancel().catch(() => {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+359
to
+385
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [🟠 MEDIUM] High Severity Issue: Missing Cleanup on Fetch Exception Suggested change:
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[🔵 LOW] Code Quality Issue: Missing Comment for
anyTypeAccording to the TypeScript type standards,
anytype should be avoided. If its use is strictly necessary, an explanatory comment should be provided to clarify whyRecord<string, any>is needed here (e.g., to easily read and mutate arbitrary JSON properties likecontentorcache_controllater).Suggested change:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in
84bc9fa0cf: added a comment onisRecordexplaining the deliberateRecord<string, any>(arbitrary request-body JSON navigated and mutated without a fixed schema).