@bitkyc08/opencodex 2.59.0 → 2.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/index-BTuCbqQd.css +1 -0
- package/gui/dist/assets/index-DoBVdPHP.js +134 -0
- package/gui/dist/index.html +2 -2
- package/package.json +4 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +4 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/turn.ts +22 -2
- package/src/adapters/command-code.ts +50 -3
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/request-builder.ts +7 -1
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +3 -1
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +50 -13
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +29 -5
- package/src/adapters/google-http.ts +49 -10
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +37 -6
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +5 -1
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +15 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/bridge/sse.ts +3 -151
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/dispatch.ts +3 -3
- package/src/cli/doctor.ts +28 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +7 -2
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +2 -2
- package/src/client/runtime.ts +26 -2
- package/src/codex/account-store.ts +65 -0
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +19 -2
- package/src/codex/catalog/retained-sync.ts +22 -26
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +20 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/model-cache.ts +47 -0
- package/src/codex/model-entitlements.ts +29 -10
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing.ts +7 -1
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +18 -0
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +4 -0
- package/src/config/schema/leaf-validators.ts +12 -0
- package/src/config.ts +2 -2
- package/src/generated/compatibility-version.json +249 -169
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/mutation-plan.ts +815 -0
- package/src/integrations/writer.ts +85 -99
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +23 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-outbound.ts +50 -2
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +3 -107
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +66 -18
- package/src/providers/model-rename-migration.ts +55 -1
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +1 -1
- package/src/providers/registry/entries-core.ts +39 -17
- package/src/providers/registry/entries-extended.ts +35 -1
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +9 -0
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +91 -115
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/auth-cors.ts +3 -0
- package/src/server/chat-completions.ts +5 -2
- package/src/server/claude-messages.ts +6 -3
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/index/serve-options.ts +86 -29
- package/src/server/index/spend-ledger-lifecycle.ts +66 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +16 -13
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/route-registry.ts +14 -0
- package/src/server/management/shared.ts +10 -3
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +15 -1
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +119 -10
- package/src/server/request-log.ts +55 -2
- package/src/server/request-metrics.ts +236 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/compact.ts +32 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +1 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/passthrough-delivery.ts +31 -14
- package/src/server/responses/passthrough-dispatch.ts +36 -9
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +67 -17
- package/src/server/responses/request-send-budget.ts +5 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +2 -2
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-undeclared-tool-guard.ts +31 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/tray/windows-tray.ps1 +155 -3
- package/src/types/config.ts +11 -3
- package/src/types/provider.ts +18 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +14 -0
- package/src/types.ts +1 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* is idempotent; ambiguous transport failures still propagate unchanged.
|
|
6
6
|
*/
|
|
7
7
|
import { parseRetryAfterFromMessage } from '../../../lib/retry-delay.js';
|
|
8
|
-
import {
|
|
8
|
+
import type { AdapterFetchContext } from '../../base.js';
|
|
9
|
+
import { createAdapterPhysicalSend } from '../../physical-send.js';
|
|
10
|
+
import { abortError, SendBudgetExhaustedError, sleepWithAbort } from '../../../lib/upstream-retry.js';
|
|
9
11
|
import { CloudChatError, streamChatEvents, type CloudChatEvent, type CloudChatRequest } from './chat.js';
|
|
10
12
|
|
|
11
13
|
/** 1 initial attempt plus at most 2 replays. */
|
|
@@ -33,6 +35,11 @@ export interface StatedResetRetryOptions {
|
|
|
33
35
|
maxReplays?: number;
|
|
34
36
|
/** CUMULATIVE wait allowance, not a fresh allowance on every failure. */
|
|
35
37
|
maxWaitMs?: number;
|
|
38
|
+
/** Request-wide execution authority. Absent preserves context-free callers' unlimited fetch. */
|
|
39
|
+
execution?: Pick<
|
|
40
|
+
AdapterFetchContext,
|
|
41
|
+
"executor" | "sendBudget" | "onPhysicalSend" | "onRecoveryWithheld"
|
|
42
|
+
>;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
function replayLimit(value: number | undefined): number {
|
|
@@ -59,15 +66,34 @@ export async function* streamChatEventsWithResetRetry(
|
|
|
59
66
|
const sleep = options?.sleep ?? sleepWithAbort;
|
|
60
67
|
const maxReplays = replayLimit(options?.maxReplays);
|
|
61
68
|
const maxWaitMs = waitLimit(options?.maxWaitMs);
|
|
69
|
+
const execution = options?.execution;
|
|
70
|
+
// One sender owns the whole invocation so ordinals span the initial POST and both replays.
|
|
71
|
+
// Its executor remains lazy: replay admission happens after the provider-stated wait, never
|
|
72
|
+
// while a reservation could be held for up to an hour.
|
|
73
|
+
const send = execution
|
|
74
|
+
? createAdapterPhysicalSend({ ...execution, abortSignal: req.signal })
|
|
75
|
+
: undefined;
|
|
62
76
|
let replays = 0;
|
|
63
77
|
let waitedMs = 0;
|
|
78
|
+
let replaySourceError: CloudChatError | undefined;
|
|
64
79
|
while (true) {
|
|
65
80
|
// Check again after sleeping: cancellation can race with timer completion.
|
|
66
81
|
// A pre-aborted request must not even enter a custom transport.
|
|
67
82
|
if (req.signal?.aborted) throw abortError(req.signal);
|
|
68
83
|
let yielded = false;
|
|
69
84
|
try {
|
|
70
|
-
|
|
85
|
+
const recovery = replays > 0 ? "rate-limit-429" as const : undefined;
|
|
86
|
+
const attemptRequest = send
|
|
87
|
+
? {
|
|
88
|
+
...req,
|
|
89
|
+
executor: ((input, init) => send({
|
|
90
|
+
url: typeof input === "string" ? input : input instanceof URL ? input.href : input.url,
|
|
91
|
+
...(recovery ? { sendClass: "auth-recovery" as const, recovery } : {}),
|
|
92
|
+
dispatch: executor => executor(input, init),
|
|
93
|
+
})) as typeof globalThis.fetch,
|
|
94
|
+
}
|
|
95
|
+
: req;
|
|
96
|
+
for await (const event of stream(attemptRequest)) {
|
|
71
97
|
// Latch before yielding, so a consumer-injected error is post-output.
|
|
72
98
|
yielded = true;
|
|
73
99
|
yield event;
|
|
@@ -75,14 +101,24 @@ export async function* streamChatEventsWithResetRetry(
|
|
|
75
101
|
return;
|
|
76
102
|
} catch (error) {
|
|
77
103
|
if (req.signal?.aborted) throw abortError(req.signal);
|
|
78
|
-
|
|
104
|
+
if (!yielded && error instanceof SendBudgetExhaustedError && replaySourceError) {
|
|
105
|
+
// The provider's refusal is the real upstream answer. A local cap can withhold its
|
|
106
|
+
// recovery, but replacing the 429 would erase the status and stated reset metadata.
|
|
107
|
+
execution?.onRecoveryWithheld?.({ reason: "retry-send-budget" });
|
|
108
|
+
throw replaySourceError;
|
|
109
|
+
}
|
|
110
|
+
const retryableError = !yielded
|
|
79
111
|
&& error instanceof CloudChatError
|
|
80
112
|
&& error.status === 429
|
|
81
|
-
?
|
|
113
|
+
? error
|
|
114
|
+
: undefined;
|
|
115
|
+
const waitSec = retryableError
|
|
116
|
+
? parseRetryAfterFromMessage(retryableError.message)
|
|
82
117
|
: undefined;
|
|
83
118
|
const waitMs = waitSec === undefined ? undefined : waitSec * 1000;
|
|
84
119
|
if (
|
|
85
|
-
|
|
120
|
+
retryableError === undefined
|
|
121
|
+
|| waitMs === undefined
|
|
86
122
|
|| replays >= maxReplays
|
|
87
123
|
|| waitMs > maxWaitMs - waitedMs
|
|
88
124
|
) {
|
|
@@ -91,6 +127,7 @@ export async function* streamChatEventsWithResetRetry(
|
|
|
91
127
|
throw error;
|
|
92
128
|
}
|
|
93
129
|
replays += 1;
|
|
130
|
+
replaySourceError = retryableError;
|
|
94
131
|
// Charge the complete scheduled wait once, before sleeping. This is a
|
|
95
132
|
// sleep allowance, not a wall-clock deadline on generation or timer
|
|
96
133
|
// scheduling: waking a few milliseconds late must not reject an already
|
package/src/adapters/devin.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { getCachedCatalog, type CacheEntry } from "./devin/cloud-direct/catalog"
|
|
|
15
15
|
import { collapseDevinModelUid } from "./devin/live-models";
|
|
16
16
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
17
17
|
import { DEVIN_DEFAULT_API_SERVER, resolveDevinApiServer } from "../oauth/devin";
|
|
18
|
+
import { isProviderIssuedThinkingSignature } from "../responses/reasoning-envelope";
|
|
19
|
+
import { SendBudgetExhaustedError } from "../lib/upstream-retry";
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Combine two usage frames from one turn by keeping the larger count per field.
|
|
@@ -253,6 +255,17 @@ function textFromParts(content: string | OcxContentPart[] | undefined): string {
|
|
|
253
255
|
return content.map((part) => (part.type === "text" ? part.text : "")).filter(Boolean).join("\n");
|
|
254
256
|
}
|
|
255
257
|
|
|
258
|
+
const MAX_DEVIN_REMOTE_IMAGE_URL_CHARS = 8_192;
|
|
259
|
+
|
|
260
|
+
function boundedDevinRemoteImageReference(imageUrl: string): string | undefined {
|
|
261
|
+
if (imageUrl.length > MAX_DEVIN_REMOTE_IMAGE_URL_CHARS) return undefined;
|
|
262
|
+
try {
|
|
263
|
+
return new URL(imageUrl).protocol === "https:" ? imageUrl : undefined;
|
|
264
|
+
} catch {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
256
269
|
/**
|
|
257
270
|
* Convert inbound content parts to the multimodal shape the wire encoder accepts.
|
|
258
271
|
*
|
|
@@ -261,9 +274,10 @@ function textFromParts(content: string | OcxContentPart[] | undefined): string {
|
|
|
261
274
|
* text-only string and a message whose only content was an image was dropped
|
|
262
275
|
* entirely, which is why a pasted screenshot killed the turn and the only
|
|
263
276
|
* workaround was running OCR before sending. A data: URL carries everything
|
|
264
|
-
* field #10 needs; a remote https URL cannot be inlined without a fetch,
|
|
265
|
-
* stays as an explicit text reference rather than pretending the model can
|
|
266
|
-
* a picture it cannot.
|
|
277
|
+
* field #10 needs; a bounded remote https URL cannot be inlined without a fetch,
|
|
278
|
+
* so it stays as an explicit text reference rather than pretending the model can
|
|
279
|
+
* see a picture it cannot. Unsupported and oversized references become a fixed
|
|
280
|
+
* omission marker, never attacker-sized prompt text. Video has no Devin field.
|
|
267
281
|
*/
|
|
268
282
|
function mapOcxContentToWire(content: string | OcxContentPart[] | undefined): string | ContentPart[] {
|
|
269
283
|
if (typeof content === "string" || !Array.isArray(content)) return content ?? "";
|
|
@@ -274,7 +288,13 @@ function mapOcxContentToWire(content: string | OcxContentPart[] | undefined): st
|
|
|
274
288
|
} else if (part.type === "image") {
|
|
275
289
|
const m = part.imageUrl.match(/^data:([^;]+);base64,(.+)$/);
|
|
276
290
|
if (m) out.push({ type: "image", mimeType: m[1]!, base64Data: m[2]! });
|
|
277
|
-
else
|
|
291
|
+
else {
|
|
292
|
+
const remoteReference = boundedDevinRemoteImageReference(part.imageUrl);
|
|
293
|
+
out.push({
|
|
294
|
+
type: "text",
|
|
295
|
+
text: remoteReference ? `[image url: ${remoteReference}]` : "[image omitted: unsupported or oversized URL]",
|
|
296
|
+
});
|
|
297
|
+
}
|
|
278
298
|
}
|
|
279
299
|
}
|
|
280
300
|
return out;
|
|
@@ -310,23 +330,26 @@ function assistantText(message: OcxAssistantMessage): string {
|
|
|
310
330
|
* clients of the same service write #11 thinking with #12 signature and #18
|
|
311
331
|
* signature_type on the assistant prompt.
|
|
312
332
|
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
333
|
+
* Field #12 attests the exact text at #11, and the wire has room for one pair.
|
|
334
|
+
* Every block that carries text is replayed, so the chain stays intact; the
|
|
335
|
+
* signature rides along only when the text being replayed IS the text it
|
|
336
|
+
* attests, which is exactly the single-block case. Several independently signed
|
|
337
|
+
* blocks send an unsigned prompt rather than pairing one block's attestation
|
|
338
|
+
* with another block's words. A signature-only block attests encrypted thinking
|
|
339
|
+
* that is not being replayed at all, so it is not one of these blocks and
|
|
340
|
+
* cannot contribute the pair.
|
|
315
341
|
*/
|
|
316
342
|
function assistantThinking(
|
|
317
343
|
message: OcxAssistantMessage,
|
|
318
344
|
): { thinking?: string; signature?: string } {
|
|
319
345
|
const blocks = message.content.filter(
|
|
320
346
|
(part): part is Extract<typeof part, { type: "thinking" }> => part.type === "thinking",
|
|
321
|
-
);
|
|
347
|
+
).filter(part => Boolean(part.thinking));
|
|
322
348
|
if (blocks.length === 0) return {};
|
|
323
|
-
const
|
|
324
|
-
// Only one signature can ride the prompt, so take the last block that has
|
|
325
|
-
// one: that is the block the turn actually ended on.
|
|
326
|
-
const signature = blocks.filter(b => b.signature).at(-1)?.signature;
|
|
349
|
+
const signature = blocks.length === 1 ? blocks[0]!.signature : undefined;
|
|
327
350
|
return {
|
|
328
|
-
|
|
329
|
-
...(signature ? { signature } : {}),
|
|
351
|
+
thinking: blocks.map(part => part.thinking).join("\n"),
|
|
352
|
+
...(isProviderIssuedThinkingSignature(signature) ? { signature } : {}),
|
|
330
353
|
};
|
|
331
354
|
}
|
|
332
355
|
|
|
@@ -490,6 +513,10 @@ export function createDevinAdapter(
|
|
|
490
513
|
|
|
491
514
|
return {
|
|
492
515
|
name: "devin",
|
|
516
|
+
// Every GetChatMessage send, including the first, is admitted through the shared budget and
|
|
517
|
+
// reported from the executor that dispatches it. The caller therefore leaves the first
|
|
518
|
+
// send's accounting here rather than logging it before admission can refuse it.
|
|
519
|
+
reportsPhysicalSends: true,
|
|
493
520
|
|
|
494
521
|
buildRequest() {
|
|
495
522
|
return {
|
|
@@ -584,6 +611,13 @@ export function createDevinAdapter(
|
|
|
584
611
|
...(typeof parsed.options.topP === "number" ? { topP: parsed.options.topP } : {}),
|
|
585
612
|
},
|
|
586
613
|
signal: incoming.abortSignal,
|
|
614
|
+
}, {
|
|
615
|
+
execution: {
|
|
616
|
+
executor: incoming.providerFetch,
|
|
617
|
+
sendBudget: incoming.sendBudget,
|
|
618
|
+
onPhysicalSend: incoming.onPhysicalSend,
|
|
619
|
+
onRecoveryWithheld: incoming.onRecoveryWithheld,
|
|
620
|
+
},
|
|
587
621
|
})) {
|
|
588
622
|
if (incoming.abortSignal?.aborted) {
|
|
589
623
|
// Emitting nothing here left the bridge to synthesize adapter_eof.
|
|
@@ -662,6 +696,9 @@ export function createDevinAdapter(
|
|
|
662
696
|
emit({ type: "error", message: DEVIN_CLIENT_CLOSED_MESSAGE, status: 499, retryable: false, ...(usage ? { usage } : {}) });
|
|
663
697
|
return;
|
|
664
698
|
}
|
|
699
|
+
// The Responses boundary already maps this local refusal to its structured 429 code.
|
|
700
|
+
// Converting it to an adapter event would make it an ordinary untyped upstream error.
|
|
701
|
+
if (error instanceof SendBudgetExhaustedError) throw error;
|
|
665
702
|
const message = error instanceof CloudChatError
|
|
666
703
|
? ("Devin cloud error" + (error.code ? " " + error.code : "") + ": " + error.message)
|
|
667
704
|
: error instanceof Error ? error.message : String(error);
|
|
@@ -629,7 +629,7 @@ export function antigravityUsesReplayCache(model: string): boolean {
|
|
|
629
629
|
* neither the wire metadata nor the replay cache can supply a real one, this is the official
|
|
630
630
|
* validator-bypass token.
|
|
631
631
|
*/
|
|
632
|
-
const THOUGHT_SIGNATURE_BYPASS = "skip_thought_signature_validator";
|
|
632
|
+
export const THOUGHT_SIGNATURE_BYPASS = "skip_thought_signature_validator";
|
|
633
633
|
|
|
634
634
|
/**
|
|
635
635
|
* True when the model speaks the Gemini wire dialect that requires a thought signature on the
|
|
@@ -77,7 +77,7 @@ export function antigravitySessionId(parsed: OcxParsedRequest): string {
|
|
|
77
77
|
//
|
|
78
78
|
// Clients that send no thread header keep the text anchor and its instability; this is a scoped
|
|
79
79
|
// repair, not a universal one.
|
|
80
|
-
const text =
|
|
80
|
+
const text = codexThreadAnchor(parsed).preimage ?? firstUserText(parsed);
|
|
81
81
|
if (!text) return `-${Math.floor(Math.random() * 9e18).toString()}`;
|
|
82
82
|
const digest = createHash("sha256").update(text, "utf8").digest();
|
|
83
83
|
const masked = digest.readBigUInt64BE(0) & 0x7fffffffffffffffn;
|
|
@@ -96,7 +96,10 @@ export function antigravitySessionId(parsed: OcxParsedRequest): string {
|
|
|
96
96
|
* functionCall identity (name+args), so a shared id does not misattribute them. Instability, not
|
|
97
97
|
* collision, is the failure mode this function exists to prevent.
|
|
98
98
|
*/
|
|
99
|
-
function
|
|
99
|
+
function codexThreadAnchor(parsed: OcxParsedRequest): {
|
|
100
|
+
kind: "parent-and-own" | "parent-only" | "absent";
|
|
101
|
+
preimage?: string;
|
|
102
|
+
} {
|
|
100
103
|
// `_clientThreadId` carries `x-codex-parent-thread-id`, which every parallel child of one
|
|
101
104
|
// parent presents identically, so anchoring on it alone collapsed concurrent children onto a
|
|
102
105
|
// single upstream Cloud Code Assist session (#5033).
|
|
@@ -113,9 +116,9 @@ function clientThreadAnchor(parsed: OcxParsedRequest): string | undefined {
|
|
|
113
116
|
// survive both compaction and restart.
|
|
114
117
|
const own = parsed._codexOwnThreadId?.trim();
|
|
115
118
|
const parent = parsed._clientThreadId?.trim();
|
|
116
|
-
if (own && parent) return `codex-thread:${parent}\u0000${own}
|
|
119
|
+
if (own && parent) return { kind: "parent-and-own", preimage: `codex-thread:${parent}\u0000${own}` };
|
|
117
120
|
// Parent-only clients keep the anchor they already had.
|
|
118
|
-
if (parent) return `codex-thread:${parent}
|
|
121
|
+
if (parent) return { kind: "parent-only", preimage: `codex-thread:${parent}` };
|
|
119
122
|
// A parentless ROOT deliberately omits the parent header. `src/server/context-history.ts` says
|
|
120
123
|
// so in as many words: root model requests use (session-id=root, thread-id=root) and do not
|
|
121
124
|
// fabricate a parent key. It has no pair to key on, and #5054's claim that a root presents
|
|
@@ -125,7 +128,28 @@ function clientThreadAnchor(parsed: OcxParsedRequest): string | undefined {
|
|
|
125
128
|
// preference: durable Antigravity replay state is keyed by model plus session id, and moving a
|
|
126
129
|
// root's anchor on upgrade strands every signature stored under the old session — the exact
|
|
127
130
|
// instability this derivation exists to avoid, introduced while fixing sharing.
|
|
128
|
-
return
|
|
131
|
+
return { kind: "absent" };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Which class of anchor {@link antigravitySessionId} derives this request's session id from.
|
|
136
|
+
*
|
|
137
|
+
* The id itself identifies a live conversation and is a Google-visible wire value, so it is not
|
|
138
|
+
* something a user can paste into a public bug report. The class is: it distinguishes the four
|
|
139
|
+
* stability regimes above — a Codex parent/child pair, a parent-only client, the content-derived
|
|
140
|
+
* text anchor that compaction can move, and the random fallback that changes every turn — while
|
|
141
|
+
* carrying no identity at all.
|
|
142
|
+
*
|
|
143
|
+
* It reads the SAME decision the id derivation reads, so the two cannot disagree about which
|
|
144
|
+
* regime a request is in. What it does not do is change how the id itself is derived: nothing
|
|
145
|
+
* about a Google-visible wire value moves because a diagnostic wants to describe it.
|
|
146
|
+
*/
|
|
147
|
+
export type AntigravitySessionAnchor = "parent-and-own" | "parent-only" | "text" | "none";
|
|
148
|
+
|
|
149
|
+
export function antigravitySessionAnchor(parsed: OcxParsedRequest): AntigravitySessionAnchor {
|
|
150
|
+
const anchor = codexThreadAnchor(parsed);
|
|
151
|
+
if (anchor.kind !== "absent") return anchor.kind;
|
|
152
|
+
return firstUserText(parsed) ? "text" : "none";
|
|
129
153
|
}
|
|
130
154
|
|
|
131
155
|
/** A Gemini content part as it appears in an Antigravity request body. */
|
|
@@ -2,8 +2,10 @@ import type { AdapterFetchContext, AdapterRequest } from "./base";
|
|
|
2
2
|
import { createAdapterPhysicalSend } from "./physical-send";
|
|
3
3
|
import type { SendClass } from "../lib/request-execution-budget";
|
|
4
4
|
import type { AttemptRecoveryKind } from "../usage/log";
|
|
5
|
+
import { debugProviderDiagnosticLazy } from "../lib/debug";
|
|
5
6
|
import { isQuotaExhaustedBody, retryableGoogleStatus, safeGoogleHttpErrorMessage } from "./google-errors";
|
|
6
|
-
import {
|
|
7
|
+
import { repairGoogleInvalidRequestBodyWithReport } from "./google-wire-compiler";
|
|
8
|
+
import type { GoogleToolSchemaPolicy, GoogleToolSchemaProfile } from "./google-tool-schema";
|
|
7
9
|
import { normalizeUpstreamHttpErrorResponse, readDisplaySafeErrorPayloadText } from "./upstream-http-error";
|
|
8
10
|
import {
|
|
9
11
|
abortError,
|
|
@@ -22,6 +24,8 @@ const GOOGLE_RETRY_MAX_MS = 2_000;
|
|
|
22
24
|
export interface GoogleRetryOptions {
|
|
23
25
|
/** Repair-and-replay structurally invalid 400 bodies (Vertex/Antigravity behavior). */
|
|
24
26
|
repairInvalid400?: boolean;
|
|
27
|
+
toolSchemaProfile?: GoogleToolSchemaProfile;
|
|
28
|
+
toolSchemaPolicy?: GoogleToolSchemaPolicy;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
async function normalizeFinalGoogleError(label: string, res: Response, signal?: AbortSignal): Promise<Response> {
|
|
@@ -45,6 +49,8 @@ export async function fetchGoogleWithRetry(
|
|
|
45
49
|
opts: GoogleRetryOptions = {},
|
|
46
50
|
): Promise<Response> {
|
|
47
51
|
const repairInvalid400 = opts.repairInvalid400 ?? true;
|
|
52
|
+
const toolSchemaProfile = opts.toolSchemaProfile ?? { endpointClass: "vertex" };
|
|
53
|
+
const toolSchemaPolicy = opts.toolSchemaPolicy ?? "compatible";
|
|
48
54
|
const timeoutMs = ctx.timeoutMs ?? 200_000;
|
|
49
55
|
const send = createAdapterPhysicalSend(ctx);
|
|
50
56
|
let lastError: unknown;
|
|
@@ -77,10 +83,22 @@ export async function fetchGoogleWithRetry(
|
|
|
77
83
|
} catch (error) {
|
|
78
84
|
if (ctx.abortSignal?.aborted) throw error;
|
|
79
85
|
}
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
86
|
+
const repair = repairGoogleInvalidRequestBodyWithReport(activeRequest.body, payloadText, toolSchemaProfile);
|
|
87
|
+
if (repair !== undefined) {
|
|
88
|
+
if (repair.toolSchemaLoss) {
|
|
89
|
+
const changedSendAllowed = toolSchemaPolicy !== "reject-lossy";
|
|
90
|
+
debugProviderDiagnosticLazy("google", "google-tool-schema-repair", () => ({
|
|
91
|
+
...repair.toolSchemaLoss,
|
|
92
|
+
phase: "repair",
|
|
93
|
+
declarationCount: repair.toolSchemaDeclarationCount ?? 0,
|
|
94
|
+
changedSendAllowed,
|
|
95
|
+
}));
|
|
96
|
+
if (!changedSendAllowed) {
|
|
97
|
+
return ctx.returnRawErrors ? res : normalizeFinalGoogleError(label, res, ctx.abortSignal);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
82
100
|
compatibilityReplayUsed = true;
|
|
83
|
-
activeRequest = { ...activeRequest, body:
|
|
101
|
+
activeRequest = { ...activeRequest, body: repair.body };
|
|
84
102
|
pendingResponse = res;
|
|
85
103
|
sendClass = "repair";
|
|
86
104
|
attempt--; // The changed-request replay is separate from transient retry accounting.
|
|
@@ -145,16 +163,37 @@ export async function fetchGoogleWithRetry(
|
|
|
145
163
|
* demand" spikes, plus plain rate-limit 429s, both of which previously failed immediately
|
|
146
164
|
* because the default server fetch path only retries connection resets.
|
|
147
165
|
*/
|
|
148
|
-
export function fetchDirectGeminiWithRetry(
|
|
149
|
-
|
|
166
|
+
export function fetchDirectGeminiWithRetry(
|
|
167
|
+
request: AdapterRequest,
|
|
168
|
+
ctx: AdapterFetchContext = {},
|
|
169
|
+
schemaPolicy: Pick<GoogleRetryOptions, "toolSchemaProfile" | "toolSchemaPolicy"> = {},
|
|
170
|
+
): Promise<Response> {
|
|
171
|
+
return fetchGoogleWithRetry("Gemini", request, { ...ctx, returnRawErrors: true }, {
|
|
172
|
+
...schemaPolicy,
|
|
173
|
+
repairInvalid400: false,
|
|
174
|
+
});
|
|
150
175
|
}
|
|
151
176
|
|
|
152
177
|
/** Vertex AI retry wrapper. */
|
|
153
|
-
export function fetchVertexWithRetry(
|
|
154
|
-
|
|
178
|
+
export function fetchVertexWithRetry(
|
|
179
|
+
request: AdapterRequest,
|
|
180
|
+
ctx: AdapterFetchContext = {},
|
|
181
|
+
schemaPolicy: Pick<GoogleRetryOptions, "toolSchemaProfile" | "toolSchemaPolicy"> = {},
|
|
182
|
+
): Promise<Response> {
|
|
183
|
+
return fetchGoogleWithRetry("Vertex AI", request, ctx, {
|
|
184
|
+
toolSchemaProfile: { endpointClass: "vertex" },
|
|
185
|
+
...schemaPolicy,
|
|
186
|
+
});
|
|
155
187
|
}
|
|
156
188
|
|
|
157
189
|
/** Antigravity (Cloud Code Assist) retry wrapper. */
|
|
158
|
-
export function fetchAntigravityWithRetry(
|
|
159
|
-
|
|
190
|
+
export function fetchAntigravityWithRetry(
|
|
191
|
+
request: AdapterRequest,
|
|
192
|
+
ctx: AdapterFetchContext = {},
|
|
193
|
+
schemaPolicy: Pick<GoogleRetryOptions, "toolSchemaProfile" | "toolSchemaPolicy"> = {},
|
|
194
|
+
): Promise<Response> {
|
|
195
|
+
return fetchGoogleWithRetry("Antigravity", request, ctx, {
|
|
196
|
+
toolSchemaProfile: { endpointClass: "cloud-code-assist" },
|
|
197
|
+
...schemaPolicy,
|
|
198
|
+
});
|
|
160
199
|
}
|