@bitkyc08/opencodex 2.69.0 → 2.70.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/App-_MrbTrBe.js +51 -0
- package/gui/dist/assets/{Tray-uqKRqfvw.js → Tray-5PuUalET.js} +1 -1
- package/gui/dist/assets/index-DKrO2eF3.js +86 -0
- package/gui/dist/assets/quota-summary-BCLivc0Q.js +1 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-model-contract.ts +119 -0
- package/src/adapters/anthropic.ts +14 -83
- package/src/adapters/base.ts +2 -0
- package/src/adapters/command-code-project-context.ts +542 -0
- package/src/adapters/command-code-tool-text.ts +99 -67
- package/src/adapters/command-code.ts +5 -1
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/devin/cloud-direct/catalog.ts +91 -0
- package/src/adapters/devin/cloud-direct/chat.ts +78 -48
- package/src/adapters/devin/cloud-direct/tool-schema.ts +121 -0
- package/src/adapters/devin/context-overflow.ts +75 -0
- package/src/adapters/devin/live-models.ts +174 -13
- package/src/adapters/devin/reasoning-signature.ts +100 -0
- package/src/adapters/devin.ts +310 -117
- package/src/adapters/kiro/payload.ts +10 -10
- package/src/adapters/kiro-images.ts +58 -7
- package/src/adapters/openai-chat/passthrough.ts +3 -1
- package/src/adapters/openai-chat.ts +2 -1
- package/src/adapters/openai-responses/passthrough.ts +2 -1
- package/src/bridge/response-json.ts +15 -1
- package/src/bridge/sse.ts +3 -1
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/cli/account-extended.ts +31 -1
- package/src/cli/account.ts +3 -0
- package/src/cli/capabilities.ts +13 -0
- package/src/cli/client-start-fence.ts +36 -0
- package/src/cli/dispatch.ts +3 -2
- package/src/cli/index.ts +98 -117
- package/src/cli/provider-runtime.ts +23 -2
- package/src/cli/provider.ts +3 -0
- package/src/cli/resolve.ts +18 -0
- package/src/cli/start-owner-fence.ts +30 -0
- package/src/cli/stop-approval.ts +13 -1
- package/src/cli/stop-restore.ts +73 -0
- package/src/cli/tray-proxy.ts +221 -18
- package/src/cli/version-skew.ts +25 -23
- package/src/client/runtime.ts +8 -4
- package/src/codex/auth-api/account-list.ts +11 -5
- package/src/codex/auth-api/login-flow.ts +5 -3
- package/src/codex/auth-api/main-account-probe.ts +186 -110
- package/src/codex/auth-api/pool-mode-gate.ts +61 -7
- package/src/codex/auth-api/pool-quota-probe.ts +142 -90
- package/src/codex/auth-api/reset-credit-service.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +1 -0
- package/src/codex/catalog/model-hints.ts +3 -3
- package/src/codex/catalog/provider-models.ts +2 -0
- package/src/codex/low-quota-events.ts +28 -0
- package/src/codex/low-quota-observer.ts +23 -0
- package/src/codex/low-quota-protection.ts +272 -0
- package/src/codex/quota-query-backoff.ts +148 -0
- package/src/codex/quota.ts +7 -2
- package/src/config/diagnostics.ts +10 -1
- package/src/config/load-degrade.ts +17 -0
- package/src/config/mutation-lock.ts +3 -4
- package/src/config/process-state.ts +1 -1
- package/src/config/provider-validation.ts +22 -0
- package/src/config/schema/blocked-model-redirects.ts +17 -0
- package/src/config/schema/config-schema.ts +4 -1
- package/src/config/schema/leaf-validators.ts +33 -2
- package/src/config/serving-runtimes.ts +412 -0
- package/src/generated/compatibility-version.json +211 -115
- package/src/generated/model-metadata.ts +3 -3
- package/src/images/loop.ts +3 -0
- package/src/lib/errors.ts +2 -1
- package/src/lib/handled-signal-exit.ts +23 -0
- package/src/lib/request-failure-model.ts +1 -0
- package/src/lib/self-launch-argv.ts +9 -0
- package/src/lib/shadow-call.ts +5 -2
- package/src/lib/strict-semver.ts +26 -0
- package/src/lib/transient-service-path.ts +16 -0
- package/src/lib/windows-service-wrappers.ts +10 -6
- package/src/lib/winsw.ts +3 -1
- package/src/oauth/anthropic-model-routes.ts +105 -0
- package/src/oauth/anthropic-routing.ts +85 -48
- package/src/oauth/devin.ts +135 -5
- package/src/oauth/index.ts +20 -3
- package/src/oauth/kiro-terminal-failover.ts +23 -9
- package/src/oauth/pool-settings-capability.ts +10 -2
- package/src/oauth/store.ts +1 -1
- package/src/protocols/encoders/adapter-events.ts +3 -1
- package/src/providers/github-copilot-context.ts +27 -0
- package/src/providers/kiro-models.ts +4 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/quota/account-cache.ts +8 -3
- package/src/providers/quota/devin.ts +286 -0
- package/src/providers/quota/vendor-probes-oauth.ts +6 -2
- package/src/providers/quota.ts +1 -1
- package/src/providers/registry/entries-core.ts +1 -1
- package/src/providers/registry/model-seeds.ts +5 -2
- package/src/responses/compaction-images.ts +27 -0
- package/src/router.ts +104 -16
- package/src/routing/analytics.ts +1 -0
- package/src/server/auth-cors.ts +9 -1
- package/src/server/background-lifecycle.ts +21 -10
- package/src/server/chat-completions.ts +2 -1
- package/src/server/chat-native.ts +1 -0
- package/src/server/index.ts +4 -8
- package/src/server/inference/client-encoder-delivery.ts +3 -0
- package/src/server/management/context.ts +3 -0
- package/src/server/management/low-quota-routes.ts +14 -0
- package/src/server/management/oauth-account-routes.ts +21 -1
- package/src/server/management/provider-overwrite-carry.ts +79 -6
- package/src/server/management/provider-routes.ts +41 -28
- package/src/server/management/route-registry.ts +1 -0
- package/src/server/management-api.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +3 -0
- package/src/server/responses/adapter-delivery.ts +3 -0
- package/src/server/responses/adapter-dispatch.ts +20 -4
- package/src/server/responses/antigravity-validation-refusal.ts +20 -0
- package/src/server/responses/core-auth.ts +1 -0
- package/src/server/responses/core-codex-account.ts +1 -0
- package/src/server/responses/core-normalize.ts +4 -0
- package/src/server/responses/passthrough-dispatch.ts +8 -1
- package/src/server/responses/request-sidecar-auth.ts +2 -0
- package/src/server/responses/request-transport.ts +27 -10
- package/src/server/responses/response-effects.ts +4 -1
- package/src/server/responses/run-turn-execution.ts +126 -42
- package/src/server/responses/sidecar-execution.ts +4 -1
- package/src/service/diagnostics.ts +3 -1
- package/src/service/guarded-manager-target.ts +39 -12
- package/src/service/launchd.ts +14 -6
- package/src/service/repair.ts +6 -1
- package/src/service/service-child-ownership.ts +168 -0
- package/src/service/state.ts +22 -4
- package/src/service/systemd.ts +2 -2
- package/src/service/windows-guarded-manager.ts +530 -0
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service.ts +2 -0
- package/src/types/config.ts +26 -0
- package/src/types/provider.ts +12 -0
- package/src/types/request.ts +2 -0
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +13 -0
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/vision/anthropic-describe.ts +2 -1
- package/src/web-search/anthropic-executor.ts +2 -1
- package/src/web-search/loop.ts +1 -0
- package/gui/dist/assets/App-DDbgOTh6.js +0 -51
- package/gui/dist/assets/index-CuaBJkG4.js +0 -86
- package/gui/dist/assets/quota-summary-COppQSVq.js +0 -1
|
@@ -39,6 +39,7 @@ import { getCachedCatalog, ModelNotAvailableError, type CacheEntry } from './cat
|
|
|
39
39
|
import { anySignal, cancelBodyOnAbort } from '../../../lib/abort.js';
|
|
40
40
|
import { parseRetryAfterFromMessage } from '../../../lib/retry-delay.js';
|
|
41
41
|
import { resolveDevinApiBaseUrl } from '../../../oauth/devin/api-base.js';
|
|
42
|
+
import { normalizeDevinToolParameters } from './tool-schema.js';
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* Connect-RPC streaming inactivity timeout. If the cloud sends zero bytes
|
|
@@ -175,6 +176,7 @@ export function allocateCascadeId(): string {
|
|
|
175
176
|
* #3 prompt: string (text content)
|
|
176
177
|
* #4 num_tokens: int (rough estimate)
|
|
177
178
|
* #5 safe_for_code_telemetry: bool (1 = ok to log)
|
|
179
|
+
* #9 tool_result_is_error: bool (tool prompts only)
|
|
178
180
|
* #10 images: repeated ImageData (multimodal)
|
|
179
181
|
* #11 thinking: string (assistant reasoning, replayed)
|
|
180
182
|
* #12 signature: string (opaque attestation for #11)
|
|
@@ -218,6 +220,7 @@ function encodeChatMessagePrompt(
|
|
|
218
220
|
thinking?: string;
|
|
219
221
|
signature?: string;
|
|
220
222
|
signatureType?: string;
|
|
223
|
+
isError?: boolean;
|
|
221
224
|
},
|
|
222
225
|
): Buffer {
|
|
223
226
|
const textParts = content.filter((p): p is { type: 'text'; text: string } => p.type === 'text');
|
|
@@ -234,6 +237,9 @@ function encodeChatMessagePrompt(
|
|
|
234
237
|
if (opts?.toolCallId) {
|
|
235
238
|
parts.push(encodeString(7, opts.toolCallId));
|
|
236
239
|
}
|
|
240
|
+
// Accepted live on a tool prompt. Only some models act on it, so the adapter
|
|
241
|
+
// also keeps an in-band marker in the text.
|
|
242
|
+
if (opts?.isError) parts.push(encodeVarintField(9, 1));
|
|
237
243
|
// Assistant message with tool_calls: encode each as a ChatToolCall.
|
|
238
244
|
if (opts?.toolCalls && opts.toolCalls.length > 0) {
|
|
239
245
|
for (const tc of opts.toolCalls) {
|
|
@@ -257,30 +263,26 @@ function encodeChatMessagePrompt(
|
|
|
257
263
|
const SOURCE_BY_ROLE: Record<string, number> = {
|
|
258
264
|
user: 1,
|
|
259
265
|
assistant: 2,
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
// ChatMessagePrompt has source=SYSTEM. The captured LS upstream traffic
|
|
263
|
-
// shows the IDE inlines system context into the *user* prompt (source=1)
|
|
264
|
-
// wrapped in <additional_metadata>...</additional_metadata>. We collapse
|
|
265
|
-
// role:'system' messages into the next user turn before building the
|
|
266
|
-
// proto — see `collapseSystemIntoUser` below.
|
|
266
|
+
// Never sent as a prompt source: the leading system text goes in request #2,
|
|
267
|
+
// and a later system message is collapsed into the next user turn below.
|
|
267
268
|
system: 1,
|
|
268
269
|
tool: 4,
|
|
269
270
|
};
|
|
270
271
|
|
|
271
272
|
/**
|
|
272
|
-
* Collapse
|
|
273
|
-
*
|
|
274
|
-
*
|
|
273
|
+
* Collapse `role:'system'` entries that follow the conversation start into the
|
|
274
|
+
* immediately-following user message. The leading run of system messages never
|
|
275
|
+
* reaches here; it is the request's #2 system prompt. With S0 already sent as #2:
|
|
275
276
|
*
|
|
276
|
-
* [{
|
|
277
|
+
* [{user: "U1"}, {assistant: "A1"}, {system: "S1"}, {system: "S2"}, {user: "U2"}]
|
|
277
278
|
*
|
|
278
279
|
* becomes
|
|
279
280
|
*
|
|
280
|
-
* [{user: "
|
|
281
|
+
* [{user: "U1"}, {assistant: "A1"}, {user: "<system>\nS1\n\nS2\n</system>\nU2"}]
|
|
281
282
|
*
|
|
282
283
|
* If there's no following user message, the trailing system messages get
|
|
283
|
-
* appended as a synthesized user turn.
|
|
284
|
+
* appended as a synthesized user turn. A request made only of system messages
|
|
285
|
+
* keeps no #2 and comes through here whole, so its prompt list is never empty.
|
|
284
286
|
*/
|
|
285
287
|
function collapseSystemIntoUser(messages: ChatHistoryItem[]): ChatHistoryItem[] {
|
|
286
288
|
const out: ChatHistoryItem[] = [];
|
|
@@ -332,10 +334,15 @@ function collapseSystemIntoUser(messages: ChatHistoryItem[]): ChatHistoryItem[]
|
|
|
332
334
|
* CompletionConfiguration — mirrors the LS-shipped defaults, lets the caller
|
|
333
335
|
* override the obvious knobs.
|
|
334
336
|
*/
|
|
335
|
-
/** Output cap when the caller named
|
|
337
|
+
/** Output cap when neither the caller nor the catalog named one. */
|
|
336
338
|
const DEFAULT_MAX_OUTPUT_TOKENS = 8192;
|
|
337
|
-
/**
|
|
338
|
-
|
|
339
|
+
/**
|
|
340
|
+
* CompletionConfiguration #3 is `max_newlines`, not a token count and not an
|
|
341
|
+
* input ceiling: live, a value of 5 did not truncate a 25-line answer. It is
|
|
342
|
+
* still sent, at the value every turn has carried, so the request shape the
|
|
343
|
+
* service accepts does not change.
|
|
344
|
+
*/
|
|
345
|
+
const MAX_NEWLINES = 128_000;
|
|
339
346
|
|
|
340
347
|
/**
|
|
341
348
|
* Cognition rejects a temperature of exactly 0 with the same opaque internal
|
|
@@ -353,7 +360,6 @@ function safeTemperature(value: number | undefined): number {
|
|
|
353
360
|
|
|
354
361
|
function encodeCompletionConfiguration(opts: {
|
|
355
362
|
maxOutputTokens?: number;
|
|
356
|
-
maxInputTokens?: number;
|
|
357
363
|
temperature?: number;
|
|
358
364
|
topK?: number;
|
|
359
365
|
topP?: number;
|
|
@@ -365,15 +371,15 @@ function encodeCompletionConfiguration(opts: {
|
|
|
365
371
|
};
|
|
366
372
|
// Tag map, verified by building the same turn with a working client and
|
|
367
373
|
// diffing the encoded messages field by field: #2 is the OUTPUT cap and #3 is
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
374
|
+
// max_newlines. This layout once had those two swapped, so a caller's output
|
|
375
|
+
// cap landed in #3 and a large value in #2, and the request came back as an
|
|
376
|
+
// opaque "an internal error occurred" — for every turn, on every account,
|
|
377
|
+
// which is why free and paid failed identically. #6 and #11 are not part of
|
|
378
|
+
// the message the service accepts.
|
|
373
379
|
return Buffer.concat([
|
|
374
380
|
encodeVarintField(1, 1),
|
|
375
381
|
encodeVarintField(2, opts.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS),
|
|
376
|
-
encodeVarintField(3,
|
|
382
|
+
encodeVarintField(3, MAX_NEWLINES),
|
|
377
383
|
enc64(5, safeTemperature(opts.temperature)),
|
|
378
384
|
encodeVarintField(7, opts.topK ?? 40),
|
|
379
385
|
enc64(8, opts.topP ?? 1.0),
|
|
@@ -424,6 +430,8 @@ export interface ChatHistoryItem {
|
|
|
424
430
|
thinking?: string;
|
|
425
431
|
signature?: string;
|
|
426
432
|
signature_type?: string;
|
|
433
|
+
/** For `role: 'tool'` only — the tool failed. Encoded as ChatMessagePrompt #9. */
|
|
434
|
+
is_error?: boolean;
|
|
427
435
|
}
|
|
428
436
|
|
|
429
437
|
/**
|
|
@@ -486,7 +494,7 @@ export type CloudChatEvent =
|
|
|
486
494
|
* turn produced. Without decoding it there is nothing to put in the prompt's
|
|
487
495
|
* #12 on the next turn, so the replay would always be unsigned.
|
|
488
496
|
*/
|
|
489
|
-
| { kind: 'reasoning_signature'; signature: string }
|
|
497
|
+
| { kind: 'reasoning_signature'; signature: string; signatureType?: string }
|
|
490
498
|
| { kind: 'tool_call_start'; id: string; name: string }
|
|
491
499
|
| {
|
|
492
500
|
kind: 'tool_call_args';
|
|
@@ -531,8 +539,8 @@ interface BuildArgs {
|
|
|
531
539
|
messages: ChatHistoryItem[];
|
|
532
540
|
cascadeId: string;
|
|
533
541
|
/**
|
|
534
|
-
* GetChatMessageRequest #
|
|
535
|
-
* the working client only reuses one across a later tool loop.
|
|
542
|
+
* GetChatMessageRequest #17 prompt_id. Optional because it is omitted on a
|
|
543
|
+
* first turn; the working client only reuses one across a later tool loop.
|
|
536
544
|
*/
|
|
537
545
|
promptId?: string;
|
|
538
546
|
sessionId: string;
|
|
@@ -543,7 +551,6 @@ interface BuildArgs {
|
|
|
543
551
|
requestType?: number;
|
|
544
552
|
completionOpts?: {
|
|
545
553
|
maxOutputTokens?: number;
|
|
546
|
-
maxInputTokens?: number;
|
|
547
554
|
temperature?: number;
|
|
548
555
|
topK?: number;
|
|
549
556
|
topP?: number;
|
|
@@ -636,16 +643,20 @@ export function sanitizeToolDescriptionForCognitionForTests(description: string)
|
|
|
636
643
|
return sanitizeToolDescriptionForCognition(description);
|
|
637
644
|
}
|
|
638
645
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
646
|
+
/** Description as transmitted on the Cognition wire, also used by overflow estimation. */
|
|
647
|
+
export function prepareToolDescriptionForCognition(description: string): string {
|
|
648
|
+
const rawDesc = sanitizeToolDescriptionForCognition(description);
|
|
649
|
+
return rawDesc.length > MAX_TOOL_DESC_LEN
|
|
650
|
+
? rawDesc.slice(0, MAX_TOOL_DESC_LEN - 24) + '\n…(truncated for cloud)'
|
|
651
|
+
: rawDesc;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function encodeToolDef(tool: ToolDef, modelUid: string): Buffer {
|
|
655
|
+
const desc = prepareToolDescriptionForCognition(tool.description ?? '');
|
|
645
656
|
return Buffer.concat([
|
|
646
657
|
encodeString(1, tool.name),
|
|
647
658
|
encodeString(2, desc),
|
|
648
|
-
encodeString(3, JSON.stringify(tool.parameters ?? {})),
|
|
659
|
+
encodeString(3, JSON.stringify(normalizeDevinToolParameters(modelUid, tool.parameters ?? {}))),
|
|
649
660
|
]);
|
|
650
661
|
}
|
|
651
662
|
|
|
@@ -664,9 +675,22 @@ function buildGetChatMessageRequest(args: BuildArgs): Buffer {
|
|
|
664
675
|
cloudChatShape: true,
|
|
665
676
|
});
|
|
666
677
|
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
|
|
678
|
+
// The leading system messages become request #2. Measured live on swe-1-6
|
|
679
|
+
// with a ~6.7k-token system prompt: turn 2 read 6688 of 6715 prompt tokens
|
|
680
|
+
// from cache in #2, against 7072 of 7097 when the same text was collapsed
|
|
681
|
+
// into the first user prompt, so the cache ratio is unchanged and the prompt
|
|
682
|
+
// is smaller. The model obeyed an instruction given only in #2.
|
|
683
|
+
// A request with only system text keeps it as a user prompt: #2 alone would
|
|
684
|
+
// leave the request with no prompt at all.
|
|
685
|
+
const firstNonSystem = args.messages.findIndex((m) => m.role !== 'system');
|
|
686
|
+
const leadingSystem = firstNonSystem === -1 ? [] : args.messages.slice(0, firstNonSystem);
|
|
687
|
+
const systemPrompt = leadingSystem
|
|
688
|
+
.map((m) => normalizeContent(m.content)
|
|
689
|
+
.filter((p): p is { type: 'text'; text: string } => p.type === 'text')
|
|
690
|
+
.map((p) => p.text).join('\n'))
|
|
691
|
+
.filter(Boolean)
|
|
692
|
+
.join('\n\n');
|
|
693
|
+
const collapsed = collapseSystemIntoUser(args.messages.slice(leadingSystem.length));
|
|
670
694
|
const promptParts = collapsed.map((m) =>
|
|
671
695
|
encodeMessage(
|
|
672
696
|
3,
|
|
@@ -683,6 +707,7 @@ function buildGetChatMessageRequest(args: BuildArgs): Buffer {
|
|
|
683
707
|
thinking: m.role === 'assistant' ? m.thinking : undefined,
|
|
684
708
|
signature: m.role === 'assistant' ? m.signature : undefined,
|
|
685
709
|
signatureType: m.role === 'assistant' ? m.signature_type : undefined,
|
|
710
|
+
isError: m.role === 'tool' ? m.is_error : undefined,
|
|
686
711
|
},
|
|
687
712
|
),
|
|
688
713
|
),
|
|
@@ -691,7 +716,7 @@ function buildGetChatMessageRequest(args: BuildArgs): Buffer {
|
|
|
691
716
|
const completion = encodeCompletionConfiguration(args.completionOpts ?? {});
|
|
692
717
|
|
|
693
718
|
const toolParts: Buffer[] = (args.tools ?? []).map((t) =>
|
|
694
|
-
encodeMessage(10, encodeToolDef(t)),
|
|
719
|
+
encodeMessage(10, encodeToolDef(t, args.modelUid)),
|
|
695
720
|
);
|
|
696
721
|
|
|
697
722
|
// Field layout from mitm capture of the LS:
|
|
@@ -701,15 +726,15 @@ function buildGetChatMessageRequest(args: BuildArgs): Buffer {
|
|
|
701
726
|
// #8 completion_configuration
|
|
702
727
|
// #10 tools (repeated ChatToolDefinition)
|
|
703
728
|
// #13 prompt_cache_options
|
|
729
|
+
// #15 CortexTrajectoryReference
|
|
704
730
|
// #16 cascade_id (string)
|
|
731
|
+
// #17 prompt_id (string)
|
|
705
732
|
// #21 chat_model_uid (string)
|
|
706
|
-
// #22
|
|
733
|
+
// #22 execution_id (string)
|
|
707
734
|
return Buffer.concat([
|
|
708
735
|
encodeMessage(1, metadata),
|
|
709
|
-
// #2 system_prompt is always written, empty when the caller had none.
|
|
710
|
-
|
|
711
|
-
// source=SYSTEM is refused; this field is the one the wire expects here.
|
|
712
|
-
encodeString(2, ''),
|
|
736
|
+
// #2 system_prompt is always written, empty when the caller had none.
|
|
737
|
+
encodeString(2, systemPrompt),
|
|
713
738
|
...promptParts,
|
|
714
739
|
encodeVarintField(7, args.requestType ?? 5),
|
|
715
740
|
encodeMessage(8, completion),
|
|
@@ -721,7 +746,7 @@ function buildGetChatMessageRequest(args: BuildArgs): Buffer {
|
|
|
721
746
|
// it and records real savings; sending it unconditionally matches both the
|
|
722
747
|
// native client and CLIProxyAPIPlus, which places it outside its tools gate.
|
|
723
748
|
encodeMessage(13, encodeVarintField(1, PROMPT_CACHE_EPHEMERAL)),
|
|
724
|
-
// #15
|
|
749
|
+
// #15 CortexTrajectoryReference: { id, 1, 4 }. Present on every verified
|
|
725
750
|
// request.
|
|
726
751
|
encodeMessage(15, Buffer.concat([
|
|
727
752
|
encodeString(1, crypto.randomUUID()),
|
|
@@ -731,9 +756,9 @@ function buildGetChatMessageRequest(args: BuildArgs): Buffer {
|
|
|
731
756
|
encodeString(16, args.cascadeId),
|
|
732
757
|
encodeVarintField(20, 1),
|
|
733
758
|
encodeString(21, args.modelUid),
|
|
734
|
-
// #
|
|
735
|
-
// from the second turn onward and is reused across that turn's tool
|
|
736
|
-
// fresh per-request uuid matches neither shape.
|
|
759
|
+
// #17 prompt_id is deliberately omitted. It is a user-exchange id that only
|
|
760
|
+
// appears from the second turn onward and is reused across that turn's tool
|
|
761
|
+
// loop; a fresh per-request uuid matches neither shape.
|
|
737
762
|
]);
|
|
738
763
|
}
|
|
739
764
|
|
|
@@ -792,6 +817,10 @@ export function* decodeChatFrame(proto: Buffer): Generator<CloudChatEvent> {
|
|
|
792
817
|
}
|
|
793
818
|
}
|
|
794
819
|
if (authoritativeUsage) yield authoritativeUsage;
|
|
820
|
+
let signatureType: string | undefined;
|
|
821
|
+
for (const f of iterFields(proto)) {
|
|
822
|
+
if (f.num === 21 && f.wire === 2 && Buffer.isBuffer(f.value)) signatureType = (f.value as Buffer).toString('utf8') || undefined;
|
|
823
|
+
}
|
|
795
824
|
for (const f of iterFields(proto)) {
|
|
796
825
|
if (f.num === 3 && f.wire === 2 && Buffer.isBuffer(f.value)) {
|
|
797
826
|
// Visible delta_text — what the user should SEE in the chat.
|
|
@@ -817,7 +846,8 @@ export function* decodeChatFrame(proto: Buffer): Generator<CloudChatEvent> {
|
|
|
817
846
|
if (s) yield { kind: 'reasoning', text: s };
|
|
818
847
|
} else if (f.num === 10 && f.wire === 2 && Buffer.isBuffer(f.value)) {
|
|
819
848
|
const s = (f.value as Buffer).toString('utf8');
|
|
820
|
-
|
|
849
|
+
// #21 delta_signature_type arrives in the same frame; the prompt replays it as #18.
|
|
850
|
+
if (s) yield { kind: 'reasoning_signature', signature: s, ...(signatureType ? { signatureType } : {}) };
|
|
821
851
|
} else if (f.num === 6 && f.wire === 2 && Buffer.isBuffer(f.value)) {
|
|
822
852
|
let id: string | undefined;
|
|
823
853
|
let name: string | undefined;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-schema compatibility for Gemini models behind GetChatMessage.
|
|
3
|
+
*
|
|
4
|
+
* Measured live on gemini-3-8-flash-medium: any schema node whose `type` is a
|
|
5
|
+
* JSON-Schema type array (`["string", "null"]`) is refused with an opaque
|
|
6
|
+
* `invalid_argument` on every turn, while the same union spelled as
|
|
7
|
+
* `anyOf: [{type: "string"}, {type: "null"}]` is accepted, as are `$schema`,
|
|
8
|
+
* `additionalProperties: false`, `const`, and `$ref`/`$defs`. Claude models
|
|
9
|
+
* accept type arrays, so only the Gemini family is rewritten, and only that one
|
|
10
|
+
* keyword: the full Google subset sanitizer would strip keywords this backend
|
|
11
|
+
* accepts.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export function isDevinGeminiModelUid(modelUid: string): boolean {
|
|
15
|
+
return /^gemini-/i.test(modelUid) || /^MODEL_GOOGLE_GEMINI_/i.test(modelUid);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Keys whose values are instance data, not subschemas. */
|
|
19
|
+
const DATA_KEYS = new Set(['enum', 'const', 'default', 'examples', 'example']);
|
|
20
|
+
/** Keys whose values map arbitrary names (which may be "enum" or "default") to subschemas. */
|
|
21
|
+
const SCHEMA_MAP_KEYS = new Set(['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas', 'dependencies']);
|
|
22
|
+
/** Keys that describe the whole node and stay on it rather than moving into each branch. */
|
|
23
|
+
const ANNOTATION_KEYS = new Set(['description', 'title', 'default', 'examples', 'example', '$comment', 'deprecated']);
|
|
24
|
+
|
|
25
|
+
function rewriteMap(map: unknown): unknown {
|
|
26
|
+
if (!map || typeof map !== 'object' || Array.isArray(map)) return map;
|
|
27
|
+
// A draft-7 `dependencies` entry may be a list of property names, which is data.
|
|
28
|
+
return Object.fromEntries(Object.entries(map).map(([name, schema]) => [name, Array.isArray(schema) ? schema : rewrite(schema)]));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type Schema = Record<string, unknown>;
|
|
32
|
+
const isSchema = (value: unknown): value is Schema => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `{type: [T, "null"], ...rest}` becomes `{anyOf: [{...rest, type: T}, {type: "null"}]}`,
|
|
36
|
+
* so type-specific keywords (items, properties, ...) stay attached to their type.
|
|
37
|
+
* An existing anyOf is folded in branch by branch when no branch contradicts an
|
|
38
|
+
* outer keyword, and kept beside the split under allOf when one does.
|
|
39
|
+
*/
|
|
40
|
+
function splitTypeArray(node: Schema, types: unknown[]): Schema {
|
|
41
|
+
const annotations: Schema = {};
|
|
42
|
+
const rest: Schema = {};
|
|
43
|
+
for (const [key, value] of Object.entries(node)) {
|
|
44
|
+
if (key === 'type' || key === 'anyOf') continue;
|
|
45
|
+
Object.defineProperty(ANNOTATION_KEYS.has(key) ? annotations : rest, key, { value, enumerable: true, writable: true, configurable: true });
|
|
46
|
+
}
|
|
47
|
+
const concrete = types.filter((type) => type !== 'null');
|
|
48
|
+
// An outer enum/const is still binding after the type array is split.
|
|
49
|
+
const allowsNull = concrete.length < types.length
|
|
50
|
+
&& (!Array.isArray(rest.enum) || rest.enum.includes(null))
|
|
51
|
+
&& (!Object.hasOwn(rest, 'const') || rest.const === null);
|
|
52
|
+
const existing = Array.isArray(node.anyOf) ? node.anyOf : undefined;
|
|
53
|
+
// Boolean constraints apply to every type, including null. A bare null branch
|
|
54
|
+
// would bypass them, so keep the type split and the outer schema conjunctive.
|
|
55
|
+
if (allowsNull && ['not', 'allOf', 'oneOf', 'if', 'then', 'else'].some((key) => key in rest)) {
|
|
56
|
+
return {
|
|
57
|
+
...annotations,
|
|
58
|
+
allOf: [splitTypeArray({ type: types }, types), existing ? { ...rest, anyOf: existing } : rest],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Folding merges each branch into the outer keywords, which is only exact when
|
|
62
|
+
// they never disagree: `{maxLength: 5, anyOf: [{maxLength: 50}]}` folded would
|
|
63
|
+
// loosen the outer limit. On a disagreement keep both constraints under allOf,
|
|
64
|
+
// which this backend accepts (live: gemini-3-8-flash-medium).
|
|
65
|
+
if (existing?.some((branch) => isSchema(branch) && (
|
|
66
|
+
Object.keys(branch).some((key) => key !== 'type' && key in rest && JSON.stringify(branch[key]) !== JSON.stringify(rest[key]))
|
|
67
|
+
// Keep an existing null branch's own constraints; folding it to {type:"null"}
|
|
68
|
+
// would admit values that its enum, const, or nested schema rejects.
|
|
69
|
+
|| (allowsNull && branch.type === 'null' && Object.keys(branch).some((key) => key !== 'type'))
|
|
70
|
+
|| (allowsNull && branch.type === undefined && ['enum', 'const', 'not', 'allOf', 'oneOf'].some((key) => key in branch))
|
|
71
|
+
))) {
|
|
72
|
+
return { ...annotations, allOf: [splitTypeArray({ ...rest, type: types }, types), { anyOf: existing }] };
|
|
73
|
+
}
|
|
74
|
+
const branches: unknown[] = [];
|
|
75
|
+
let nullReachable = allowsNull && !existing;
|
|
76
|
+
if (!existing) {
|
|
77
|
+
for (const type of concrete) branches.push({ ...rest, type });
|
|
78
|
+
} else {
|
|
79
|
+
for (const branch of existing) {
|
|
80
|
+
if (!isSchema(branch)) continue;
|
|
81
|
+
if (branch.type === undefined) {
|
|
82
|
+
for (const type of concrete) branches.push({ ...rest, ...branch, type });
|
|
83
|
+
if (allowsNull) nullReachable = true;
|
|
84
|
+
} else if (branch.type === 'null') {
|
|
85
|
+
if (allowsNull) nullReachable = true;
|
|
86
|
+
} else if (concrete.includes(branch.type)) {
|
|
87
|
+
branches.push({ ...rest, ...branch });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (nullReachable) branches.push({ type: 'null' });
|
|
92
|
+
// The two unions are disjoint. Keep both constraints rather than drop the type union,
|
|
93
|
+
// which would let the anyOf branches admit types the node never allowed.
|
|
94
|
+
if (branches.length === 0) return { ...annotations, allOf: [splitTypeArray({ ...rest, type: types }, types), { anyOf: existing }] };
|
|
95
|
+
if (branches.length === 1 && isSchema(branches[0])) return { ...annotations, ...branches[0] };
|
|
96
|
+
return { ...annotations, anyOf: branches };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function rewrite(node: unknown): unknown {
|
|
100
|
+
if (Array.isArray(node)) return node.map(rewrite);
|
|
101
|
+
if (!isSchema(node)) return node;
|
|
102
|
+
// fromEntries defines own properties, so a "__proto__" key stays data.
|
|
103
|
+
const out: Schema = Object.fromEntries(
|
|
104
|
+
Object.entries(node).map(([key, value]) => [
|
|
105
|
+
key,
|
|
106
|
+
DATA_KEYS.has(key) ? value : SCHEMA_MAP_KEYS.has(key) ? rewriteMap(value) : rewrite(value),
|
|
107
|
+
]),
|
|
108
|
+
);
|
|
109
|
+
if (!Array.isArray(out.type)) return out;
|
|
110
|
+
const types = out.type as unknown[];
|
|
111
|
+
if (types.length === 0) {
|
|
112
|
+
delete out.type;
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
return splitTypeArray(out, types);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Rewrite type arrays for Gemini uids; every other model gets the schema unchanged. */
|
|
119
|
+
export function normalizeDevinToolParameters(modelUid: string, parameters: unknown): unknown {
|
|
120
|
+
return isDevinGeminiModelUid(modelUid) ? rewrite(parameters) : parameters;
|
|
121
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recognise an oversized Devin history behind Cognition's opaque refusal.
|
|
3
|
+
*
|
|
4
|
+
* Measured live on swe-1-6 (200k context): a 1.2 MB or 3 MB history is refused
|
|
5
|
+
* after ~11s with `invalid_argument` and no output, the same code a bad tool
|
|
6
|
+
* schema gets. Passed through as a plain 400, Codex never learns its context is
|
|
7
|
+
* full and never compacts, so every later turn dead-ends the same way. Only a
|
|
8
|
+
* request large against the model's window is reclassified; a small request
|
|
9
|
+
* with the same code stays an ordinary 400.
|
|
10
|
+
*
|
|
11
|
+
* Boundary, binary-searched live on swe-1-6 (200k catalog window, 8192 output):
|
|
12
|
+
* accepted at 199,186 prompt tokens of prose and 200,345 of JSON, refused at
|
|
13
|
+
* ~203k of either. Raising the output cap to 32000 did not move it (195,571
|
|
14
|
+
* input tokens still accepted), so the window is not reduced by the output
|
|
15
|
+
* reservation and the threshold is the window itself.
|
|
16
|
+
*/
|
|
17
|
+
import type { AdapterEvent } from "../../types";
|
|
18
|
+
import type { ChatHistoryItem, ToolDef } from "./cloud-direct";
|
|
19
|
+
import { prepareToolDescriptionForCognition } from "./cloud-direct/chat";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Share of the window the estimate must reach. Characters per real token ran
|
|
23
|
+
* from 1.33 (Korean) through 2.31 (JSON) and 4.23 (source code) to 5.53
|
|
24
|
+
* (prose), so no character ratio separates "over the window" from "60% of it".
|
|
25
|
+
* The word-piece count below read 1.00x to 1.54x of the real count on those
|
|
26
|
+
* samples: at 0.95 every sample at the window is caught and none at 60% is.
|
|
27
|
+
*/
|
|
28
|
+
const WINDOW_SHARE = 0.95;
|
|
29
|
+
/** One token per short letter run, 1-3 digit group, or other visible character. */
|
|
30
|
+
const WORD_PIECE = /[A-Z]?[a-z]{1,8}|[A-Z]{1,8}(?![a-z])|\d{1,3}|\S/g;
|
|
31
|
+
/** Text size that counts as oversized when the model's window is unknown. */
|
|
32
|
+
const UNKNOWN_WINDOW_CHARS = 512 * 1024;
|
|
33
|
+
|
|
34
|
+
export const DEVIN_CONTEXT_OVERFLOW_MESSAGE =
|
|
35
|
+
"Devin rejected this turn and its history is at or past the model's context window. Compact the conversation or start a new session.";
|
|
36
|
+
|
|
37
|
+
/** Text the model would tokenize; image bytes are excluded so a screenshot cannot look like a long history. */
|
|
38
|
+
function requestText(messages: ChatHistoryItem[], tools: ToolDef[] | undefined): string {
|
|
39
|
+
const parts: string[] = [];
|
|
40
|
+
for (const m of messages) {
|
|
41
|
+
if (typeof m.content === "string") parts.push(m.content);
|
|
42
|
+
else for (const p of m.content) if (p.type === "text") parts.push(p.text);
|
|
43
|
+
for (const call of m.tool_calls ?? []) parts.push(call.arguments);
|
|
44
|
+
if (m.thinking) parts.push(m.thinking);
|
|
45
|
+
}
|
|
46
|
+
for (const tool of tools ?? []) parts.push(prepareToolDescriptionForCognition(tool.description ?? ""), JSON.stringify(tool.parameters ?? {}));
|
|
47
|
+
return parts.join("\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isDevinHistoryOverflow(input: {
|
|
51
|
+
code: string | undefined;
|
|
52
|
+
producedOutput: boolean;
|
|
53
|
+
contextWindow: number | undefined;
|
|
54
|
+
messages: ChatHistoryItem[];
|
|
55
|
+
tools: ToolDef[] | undefined;
|
|
56
|
+
}): boolean {
|
|
57
|
+
if (input.code !== "invalid_argument" || input.producedOutput) return false;
|
|
58
|
+
const text = requestText(input.messages, input.tools);
|
|
59
|
+
if (!input.contextWindow) return text.length >= UNKNOWN_WINDOW_CHARS;
|
|
60
|
+
let pieces = 0;
|
|
61
|
+
for (const _ of text.matchAll(WORD_PIECE)) pieces++;
|
|
62
|
+
return pieces >= input.contextWindow * WINDOW_SHARE;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Same terminal shape the Kiro adapter uses, which Codex reads as "context full, compact". */
|
|
66
|
+
export function devinContextOverflowEvent(): Extract<AdapterEvent, { type: "error" }> {
|
|
67
|
+
return {
|
|
68
|
+
type: "error",
|
|
69
|
+
message: DEVIN_CONTEXT_OVERFLOW_MESSAGE,
|
|
70
|
+
status: 400,
|
|
71
|
+
errorType: "invalid_request_error",
|
|
72
|
+
code: "context_length_exceeded",
|
|
73
|
+
retryable: false,
|
|
74
|
+
};
|
|
75
|
+
}
|