@bitkyc08/opencodex 2.12.0 → 2.13.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/README.md +1 -1
- package/gui/dist/assets/index-BHldBl6_.js +76 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +15 -0
- package/src/adapters/google-antigravity-replay.ts +269 -5
- package/src/adapters/openai-chat.ts +25 -18
- package/src/adapters/openai-responses.ts +63 -17
- package/src/bridge.ts +11 -5
- package/src/claude/inbound.ts +24 -3
- package/src/cli/account-api.ts +2 -0
- package/src/cli/account-extended.ts +262 -1
- package/src/cli/account.ts +3 -1
- package/src/cli/dispatch.ts +532 -0
- package/src/cli/doctor.ts +26 -21
- package/src/cli/help.ts +9 -278
- package/src/cli/index.ts +45 -506
- package/src/cli/lab.ts +171 -65
- package/src/cli/registry.ts +384 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/status.ts +9 -2
- package/src/cli/v2.ts +37 -2
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-store.ts +12 -1
- package/src/codex/admission.ts +2 -1
- package/src/codex/auth-api.ts +3 -2
- package/src/codex/catalog/metadata.ts +6 -4
- package/src/codex/catalog/parsing.ts +31 -7
- package/src/codex/catalog/provider-fetch.ts +2 -1
- package/src/codex/catalog/sync.ts +65 -11
- package/src/codex/features.ts +437 -31
- package/src/codex/history-job.ts +6 -11
- package/src/codex/history-provider.ts +10 -9
- package/src/codex/inject.ts +14 -1
- package/src/codex/native-profile-startup.ts +49 -3
- package/src/codex/native-residue.ts +14 -2
- package/src/codex/paths.ts +75 -1
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/shim.ts +876 -16
- package/src/codex/sync.ts +27 -2
- package/src/config.ts +148 -10
- package/src/generated/compatibility-version.json +2572 -0
- package/src/images/loop.ts +1 -1
- package/src/lab/artifacts/sanitize.ts +3 -3
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +480 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/types.ts +23 -0
- package/src/lab/events/types.ts +2 -1
- package/src/lab/events/validate.ts +2 -1
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +454 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +2 -0
- package/src/lab/ledger/store.ts +173 -0
- package/src/lab/live/executor.ts +29 -4
- package/src/lab/live/transport.ts +3 -1
- package/src/lab/observe/from-conformance.ts +12 -57
- package/src/lab/observe/from-live.ts +9 -1
- package/src/lab/paths.ts +74 -9
- package/src/lab/projection/schema.ts +14 -1
- package/src/lab/projection/verdicts.ts +89 -53
- package/src/lab/projection/verification.ts +83 -3
- package/src/lab/query/catalog.ts +34 -5
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +11 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/subject/installation-salt.ts +24 -4
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +2 -1
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/windows-elevation.ts +8 -1
- package/src/lib/windows-user-principal.ts +62 -2
- package/src/lib/winsw.ts +8 -2
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/google-antigravity.ts +27 -0
- package/src/oauth/health.ts +12 -48
- package/src/oauth/index.ts +122 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/nous.ts +749 -0
- package/src/oauth/store.ts +56 -3
- package/src/providers/codex-capacity.ts +1 -0
- package/src/providers/registry.ts +161 -16
- package/src/responses/custom-tool-compat.ts +182 -0
- package/src/responses/parser.ts +22 -0
- package/src/responses/reasoning-replay-cache.ts +168 -11
- package/src/responses/schema.ts +1 -1
- package/src/router.ts +5 -20
- package/src/routing/capability.ts +9 -3
- package/src/routing/compatibility/assemble.ts +159 -0
- package/src/routing/compatibility/behavior.ts +224 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/evaluator.ts +27 -32
- package/src/routing/profile.ts +118 -4
- package/src/routing/trace.ts +82 -28
- package/src/server/auth-cors.ts +7 -0
- package/src/server/background-lifecycle.ts +20 -7
- package/src/server/chat-completions.ts +2 -2
- package/src/server/claude-messages.ts +6 -3
- package/src/server/direct-local-http.ts +343 -0
- package/src/server/index.ts +80 -18
- package/src/server/lifecycle.ts +12 -4
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/management/agent-settings-routes.ts +27 -3
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +13 -0
- package/src/server/management/logs-usage-routes.ts +1 -0
- package/src/server/management/oauth-account-routes.ts +57 -1
- package/src/server/management/provider-routes.ts +16 -0
- package/src/server/management/routing-profile-routes.ts +12 -24
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +68 -3
- package/src/server/proxy-liveness.ts +3 -2
- package/src/server/request-decompress.ts +9 -3
- package/src/server/request-log.ts +53 -4
- package/src/server/responses/collaboration.ts +93 -17
- package/src/server/responses/compact.ts +3 -0
- package/src/server/responses/core.ts +202 -15
- package/src/server/responses/policy-fallback.ts +1 -0
- package/src/server/responses-custom-tool-repair.ts +280 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/startup-action-control.ts +8 -1
- package/src/service.ts +278 -12
- package/src/types.ts +49 -3
- package/src/usage/expected-prices.ts +30 -0
- package/src/usage/log.ts +30 -4
- package/src/usage/summary.ts +153 -2
- package/src/web-search/loop.ts +1 -1
- package/gui/dist/assets/index-Cw1_mxO8.js +0 -70
- package/src/cli/internal-dispatch.ts +0 -20
package/src/oauth/store.ts
CHANGED
|
@@ -286,15 +286,18 @@ function normalizeCredential(cred: unknown): OAuthCredentials | null {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
/**
|
|
289
|
-
* Stable
|
|
290
|
-
* single-credential stores are re-normalized on EVERY load without being persisted,
|
|
289
|
+
* Stable collision-resistant account id. MUST be deterministic for a given credential:
|
|
290
|
+
* legacy single-credential stores are re-normalized on EVERY load without being persisted,
|
|
291
291
|
* so a time-salted id would differ between two loads (getAccountSet vs
|
|
292
292
|
* getAccountCredential), surfacing as a spurious OAuthLoginRequiredError and making
|
|
293
293
|
* refresh persists silently miss the account (rotated refresh token lost).
|
|
294
|
+
*
|
|
295
|
+
* Keep 128 bits of SHA-256 rather than the historical 32-bit prefix. Existing persisted
|
|
296
|
+
* account ids are read as-is; only newly-derived ids and legacy normalization use this width.
|
|
294
297
|
*/
|
|
295
298
|
function newAccountId(cred: OAuthCredentials): string {
|
|
296
299
|
const identity = cred.accountId ?? cred.email ?? cred.refresh;
|
|
297
|
-
return createHash("sha256").update(identity).digest("hex").slice(0,
|
|
300
|
+
return createHash("sha256").update(identity).digest("hex").slice(0, 32);
|
|
298
301
|
}
|
|
299
302
|
|
|
300
303
|
function normalizeAccount(value: unknown): ProviderAccount | null {
|
|
@@ -523,6 +526,56 @@ export async function saveCredential(
|
|
|
523
526
|
}, [provider, safe]);
|
|
524
527
|
}
|
|
525
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Atomically insert or replace an identity-bearing credential and report the disposition.
|
|
531
|
+
* Importers use this instead of a read-then-save pair so duplicate detection and persistence
|
|
532
|
+
* happen inside the existing serialized temp-then-rename store mutation.
|
|
533
|
+
*/
|
|
534
|
+
export async function upsertCredentialByIdentity(
|
|
535
|
+
provider: string,
|
|
536
|
+
cred: OAuthCredentials,
|
|
537
|
+
): Promise<"inserted" | "updated"> {
|
|
538
|
+
const safe = normalizeCredential(cred);
|
|
539
|
+
if (!safe || (!safe.accountId && !safe.email)) {
|
|
540
|
+
throw new Error("Refusing to persist OAuth credential without verified identity");
|
|
541
|
+
}
|
|
542
|
+
return await mutateStore(store => {
|
|
543
|
+
const set = store[provider];
|
|
544
|
+
const matches = (account: ProviderAccount): boolean => {
|
|
545
|
+
if (safe.accountId) {
|
|
546
|
+
if (account.credential.accountId) return account.credential.accountId === safe.accountId;
|
|
547
|
+
return Boolean(
|
|
548
|
+
safe.email
|
|
549
|
+
&& account.credential.email
|
|
550
|
+
&& account.credential.email.toLowerCase() === safe.email.toLowerCase(),
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
if (account.credential.accountId) return false;
|
|
554
|
+
return Boolean(
|
|
555
|
+
safe.email
|
|
556
|
+
&& account.credential.email
|
|
557
|
+
&& account.credential.email.toLowerCase() === safe.email.toLowerCase(),
|
|
558
|
+
);
|
|
559
|
+
};
|
|
560
|
+
const existing = set?.accounts.find(matches);
|
|
561
|
+
if (existing && set) {
|
|
562
|
+
existing.credential = safe;
|
|
563
|
+
delete existing.needsReauth;
|
|
564
|
+
set.activeAccountId ??= existing.id;
|
|
565
|
+
return "updated";
|
|
566
|
+
}
|
|
567
|
+
const id = newAccountId(safe);
|
|
568
|
+
const account: ProviderAccount = { id, credential: safe, addedAt: Date.now() };
|
|
569
|
+
if (set) {
|
|
570
|
+
set.accounts.push(account);
|
|
571
|
+
set.activeAccountId ??= id;
|
|
572
|
+
} else {
|
|
573
|
+
store[provider] = { activeAccountId: id, accounts: [account] };
|
|
574
|
+
}
|
|
575
|
+
return "inserted";
|
|
576
|
+
}, [provider, safe]);
|
|
577
|
+
}
|
|
578
|
+
|
|
526
579
|
/** Remove the ACTIVE account; remaining accounts promote the first one. */
|
|
527
580
|
export async function removeCredential(provider: string): Promise<void> {
|
|
528
581
|
await mutateStore(store => {
|
|
@@ -32,6 +32,11 @@ export type InboundWire = "responses" | "chat" | "anthropic";
|
|
|
32
32
|
*/
|
|
33
33
|
export type ModelWireDefault = string | { wire: string; inbound: readonly InboundWire[] };
|
|
34
34
|
|
|
35
|
+
export interface ResponsesTerminalRepairPolicy {
|
|
36
|
+
/** Quiet time after a structurally complete output graph before synthesizing completion. */
|
|
37
|
+
graceMs: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
export type ProviderModelDiscoveryScalar = string | number | boolean;
|
|
36
41
|
|
|
37
42
|
export type ProviderModelDiscoveryPredicate =
|
|
@@ -162,6 +167,8 @@ export interface ProviderRegistryEntry {
|
|
|
162
167
|
* can omit or indefinitely delay the terminal event.
|
|
163
168
|
*/
|
|
164
169
|
modelResponsesUpstreamStreaming?: Record<string, boolean>;
|
|
170
|
+
/** Registry-only repair for a model whose native Responses stream may omit its terminal. */
|
|
171
|
+
modelResponsesTerminalRepair?: Record<string, ResponsesTerminalRepairPolicy>;
|
|
165
172
|
/**
|
|
166
173
|
* Registry-only client-facing item-id repair policy (#938), filled onto the
|
|
167
174
|
* runtime provider only when the user has no explicit policy (derive.ts);
|
|
@@ -187,8 +194,8 @@ export interface ProviderRegistryEntry {
|
|
|
187
194
|
*/
|
|
188
195
|
statelessResponses?: boolean;
|
|
189
196
|
/**
|
|
190
|
-
* Responses parser requires
|
|
191
|
-
* seeded/backfilled like other fixed
|
|
197
|
+
* Responses parser requires an unambiguous call batch and its matched result batch
|
|
198
|
+
* to stay contiguous. This is seeded/backfilled like other fixed wire capabilities.
|
|
192
199
|
*/
|
|
193
200
|
requiresAdjacentResponsesToolResults?: boolean;
|
|
194
201
|
/**
|
|
@@ -293,12 +300,6 @@ const MINIMAX_M3_REASONING_EFFORT_MAP: Record<string, string> = {
|
|
|
293
300
|
const OPENAI_GPT56_MODELS = ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
|
|
294
301
|
const OPENAI_GPT56_PRO_MODELS = ["gpt-5.6-sol-pro", "gpt-5.6-terra-pro", "gpt-5.6-luna-pro"];
|
|
295
302
|
const OPENAI_API_GPT56_CONTEXT_WINDOW = 1_050_000;
|
|
296
|
-
const OPENAI_CODEX_GPT56_CONTEXT_WINDOW = 372_000;
|
|
297
|
-
const OPENAI_GPT56_CONTEXT_WINDOWS = {
|
|
298
|
-
"gpt-5.6-sol": OPENAI_CODEX_GPT56_CONTEXT_WINDOW,
|
|
299
|
-
"gpt-5.6-terra": OPENAI_CODEX_GPT56_CONTEXT_WINDOW,
|
|
300
|
-
"gpt-5.6-luna": OPENAI_CODEX_GPT56_CONTEXT_WINDOW,
|
|
301
|
-
};
|
|
302
303
|
const OPENAI_API_GPT56_CONTEXT_WINDOWS: Record<string, number> = {
|
|
303
304
|
...Object.fromEntries([...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, OPENAI_API_GPT56_CONTEXT_WINDOW])),
|
|
304
305
|
"gpt-5.5": OPENAI_API_GPT56_CONTEXT_WINDOW,
|
|
@@ -313,6 +314,37 @@ const OPENAI_API_GPT56_VIRTUAL_MODELS: Record<string, { wireModelId: string; rea
|
|
|
313
314
|
"gpt-5.6-luna-pro": { wireModelId: "gpt-5.6-luna", reasoningMode: "pro" },
|
|
314
315
|
};
|
|
315
316
|
const OPENAI_API_GPT56_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
317
|
+
/**
|
|
318
|
+
* Daybreak program aliases. These `-latest` ids are the stable contract: OpenAI repoints
|
|
319
|
+
* them at newer snapshots over time (red -> gpt-5.6-cyber, blue -> gpt-5.6-sol as of
|
|
320
|
+
* 2026-08-11), so registering the ALIAS inherits future model swaps while a pinned
|
|
321
|
+
* snapshot id would silently go stale. Snapshot ids are deliberately absent here.
|
|
322
|
+
* Responses-only per both published endpoint tables (`v1/chat/completions` is marked
|
|
323
|
+
* Not supported) — never add these to a chat-completions provider. Access needs separate
|
|
324
|
+
* Daybreak approval and provisioning, so neither is ever a default.
|
|
325
|
+
* Verified 2026-08-11: developers.openai.com/api/docs/models/daybreak-red-latest.md
|
|
326
|
+
* and .../daybreak-blue-latest.md
|
|
327
|
+
*/
|
|
328
|
+
const OPENAI_DAYBREAK_MODELS = ["daybreak-red-latest", "daybreak-blue-latest"];
|
|
329
|
+
const OPENAI_DAYBREAK_CONTEXT_WINDOWS: Record<string, number> = {
|
|
330
|
+
"daybreak-red-latest": 400_000,
|
|
331
|
+
"daybreak-blue-latest": 1_050_000,
|
|
332
|
+
};
|
|
333
|
+
const OPENAI_DAYBREAK_MAX_INPUT_TOKENS: Record<string, number> = {
|
|
334
|
+
"daybreak-red-latest": 272_000,
|
|
335
|
+
"daybreak-blue-latest": 922_000,
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* Neither Daybreak page publishes a reasoning-effort ladder. An explicit empty array means
|
|
339
|
+
* "expose no effort control"; OMITTING the key would instead fall back to the full routed
|
|
340
|
+
* ladder (`configuredReasoningEfforts` returns undefined -> `applyReasoningLevels` uses
|
|
341
|
+
* ROUTED_REASONING_LEVELS), which would advertise efforts the models never documented.
|
|
342
|
+
* `noReasoningModels` is wrong here: both pages document reasoning-token support, so these
|
|
343
|
+
* are reasoning models with no *selectable* ladder.
|
|
344
|
+
*/
|
|
345
|
+
const OPENAI_DAYBREAK_REASONING_EFFORTS: Record<string, string[]> = Object.fromEntries(
|
|
346
|
+
OPENAI_DAYBREAK_MODELS.map(id => [id, [] as string[]]),
|
|
347
|
+
);
|
|
316
348
|
const OPENROUTER_GPT56_MODELS = OPENAI_GPT56_MODELS.map(id => `openai/${id}`);
|
|
317
349
|
// OpenRouter's live /endpoints routes report 1,050,000; keep this separate from the
|
|
318
350
|
// unverified OpenAI API-key seed. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
@@ -1052,6 +1084,45 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1052
1084
|
modelContextWindows: KIRO_MODEL_CONTEXT_WINDOWS,
|
|
1053
1085
|
modelReasoningEfforts: KIRO_MODEL_REASONING_EFFORTS,
|
|
1054
1086
|
},
|
|
1087
|
+
{
|
|
1088
|
+
// Nous Portal — Nous Research subscription gateway (same backend Hermes Agent
|
|
1089
|
+
// uses). OAuth is a device grant (src/oauth/nous.ts): the access token IS the
|
|
1090
|
+
// per-request inference JWT (scope inference:invoke), refresh tokens are
|
|
1091
|
+
// single-use and rotated on every refresh. Catalog is a mix of paid models
|
|
1092
|
+
// (billed against the Portal subscription) and `:free` slugs (e.g.
|
|
1093
|
+
// tencent/hy3:free, stepfun/step-3.7-flash:free, inclusionai/ling-3.0-flash:free);
|
|
1094
|
+
// free-tier gating is decided live by the Portal per account, so discovery
|
|
1095
|
+
// from the signed-in account is authoritative; the static seed below is the
|
|
1096
|
+
// logged-out fallback and only lists free models verified on a real account
|
|
1097
|
+
// (2026-08-10): the Portal free list is authoritative and currently has
|
|
1098
|
+
// exactly 4 :free models: tencent/hy3:free, poolside/laguna-s-2.1:free,
|
|
1099
|
+
// stepfun/step-3.7-flash:free, poolside/laguna-xs-2.1:free.
|
|
1100
|
+
// inclusionai/ling-3.0-flash:free was removed from the Portal free list
|
|
1101
|
+
// (404 on the inference API since 2026-08-07) and must not be seeded.
|
|
1102
|
+
id: "nous",
|
|
1103
|
+
label: "Nous Portal",
|
|
1104
|
+
adapter: "openai-chat",
|
|
1105
|
+
baseUrl: "https://inference-api.nousresearch.com/v1",
|
|
1106
|
+
authKind: "oauth",
|
|
1107
|
+
oauthId: "nous",
|
|
1108
|
+
featured: true,
|
|
1109
|
+
// Mixed free + paid provider: the free tier is per-model (the `:free`
|
|
1110
|
+
// slugs), not a property of the whole provider, so freeTier stays false to
|
|
1111
|
+
// avoid implying every model is free.
|
|
1112
|
+
freeTier: false,
|
|
1113
|
+
dashboardUrl: "https://portal.nousresearch.com",
|
|
1114
|
+
defaultModel: "tencent/hy3:free",
|
|
1115
|
+
liveModels: true,
|
|
1116
|
+
models: ["tencent/hy3:free", "poolside/laguna-s-2.1:free", "stepfun/step-3.7-flash:free", "poolside/laguna-xs-2.1:free"],
|
|
1117
|
+
modelDiscovery: {
|
|
1118
|
+
// Resolves against effectiveBaseUrl (registry baseUrl .../v1) to the same
|
|
1119
|
+
// canonical endpoint https://inference-api.nousresearch.com/v1/models.
|
|
1120
|
+
path: "models",
|
|
1121
|
+
maxResponseBytes: 262_144,
|
|
1122
|
+
maxModels: 512,
|
|
1123
|
+
},
|
|
1124
|
+
note: "Nous Research subscription gateway. OAuth device login with your own Portal account; mixed paid + :free models discovered live (fallback seed 2026-08-10: tencent/hy3:free, poolside/laguna-s-2.1:free, stepfun/step-3.7-flash:free, poolside/laguna-xs-2.1:free).",
|
|
1125
|
+
},
|
|
1055
1126
|
{
|
|
1056
1127
|
id: "openai-apikey",
|
|
1057
1128
|
label: "OpenAI API",
|
|
@@ -1062,16 +1133,20 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1062
1133
|
featured: true,
|
|
1063
1134
|
dashboardUrl: "https://platform.openai.com/api-keys",
|
|
1064
1135
|
defaultModel: "gpt-5.5",
|
|
1065
|
-
models: ["gpt-5.5", ...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS],
|
|
1136
|
+
models: ["gpt-5.5", ...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS, ...OPENAI_DAYBREAK_MODELS],
|
|
1066
1137
|
liveModels: true,
|
|
1067
|
-
modelContextWindows: OPENAI_API_GPT56_CONTEXT_WINDOWS,
|
|
1068
|
-
modelMaxInputTokens: OPENAI_API_GPT56_MAX_INPUT_TOKENS,
|
|
1138
|
+
modelContextWindows: { ...OPENAI_API_GPT56_CONTEXT_WINDOWS, ...OPENAI_DAYBREAK_CONTEXT_WINDOWS },
|
|
1139
|
+
modelMaxInputTokens: { ...OPENAI_API_GPT56_MAX_INPUT_TOKENS, ...OPENAI_DAYBREAK_MAX_INPUT_TOKENS },
|
|
1069
1140
|
modelInputModalities: Object.fromEntries(
|
|
1070
|
-
["gpt-5.5", ...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS
|
|
1071
|
-
|
|
1072
|
-
modelReasoningEfforts: Object.fromEntries(
|
|
1073
|
-
[...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, OPENAI_API_GPT56_REASONING_EFFORTS]),
|
|
1141
|
+
["gpt-5.5", ...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS, ...OPENAI_DAYBREAK_MODELS]
|
|
1142
|
+
.map(id => [id, ["text", "image"]]),
|
|
1074
1143
|
),
|
|
1144
|
+
modelReasoningEfforts: {
|
|
1145
|
+
...Object.fromEntries(
|
|
1146
|
+
[...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, OPENAI_API_GPT56_REASONING_EFFORTS]),
|
|
1147
|
+
),
|
|
1148
|
+
...OPENAI_DAYBREAK_REASONING_EFFORTS,
|
|
1149
|
+
},
|
|
1075
1150
|
virtualModels: OPENAI_API_GPT56_VIRTUAL_MODELS,
|
|
1076
1151
|
},
|
|
1077
1152
|
{
|
|
@@ -1102,6 +1177,15 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1102
1177
|
id: "opencode-go", label: "opencode go", adapter: "openai-chat", baseUrl: "https://opencode.ai/zen/go/v1",
|
|
1103
1178
|
authKind: "key", featured: true, dashboardUrl: "https://opencode.ai/auth", defaultModel: "kimi-k2.7-code",
|
|
1104
1179
|
jawcodeBundle: "opencode-go", note: "GLM, DeepSeek, Kimi, Qwen, MiMo…",
|
|
1180
|
+
/* [Decision Log]
|
|
1181
|
+
- 목적과 의도: Route GPT 5.6 Luna to the Responses endpoint that OpenCode Go documents for that exact model.
|
|
1182
|
+
- 기존 구현 및 제약 조건: The provider is mixed-wire but its provider-wide `openai-chat` adapter sent Luna to `/chat/completions`; explicit user `modelAdapters` entries must remain authoritative.
|
|
1183
|
+
- 검토한 주요 대안: Change the whole provider to Responses; infer the wire from model-family names; add one registry-only exact-model default.
|
|
1184
|
+
- 선택한 방식: Declare only `gpt-5.6-luna` as `openai-responses` through the existing registry default mechanism.
|
|
1185
|
+
- 다른 대안 대신 이 방식을 선택한 이유: OpenCode Go documents sibling models on Chat or Anthropic endpoints, and an exact registry default preserves both those routes and explicit opt-out precedence.
|
|
1186
|
+
- 장점, 단점 및 영향: Luna reaches `/responses` from every inbound surface without changing siblings; a future upstream endpoint change requires an evidence-backed registry update.
|
|
1187
|
+
*/
|
|
1188
|
+
modelWireDefaults: { "gpt-5.6-luna": "openai-responses" },
|
|
1105
1189
|
modelContextWindows: { "kimi-k3": KIMI_K3_STANDARD_CONTEXT_WINDOW },
|
|
1106
1190
|
modelInputModalities: { "kimi-k3": ["text", "image"] },
|
|
1107
1191
|
modelReasoningEfforts: {
|
|
@@ -1352,6 +1436,9 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1352
1436
|
// for providers that need it — re-adding one line here restores the old policy.
|
|
1353
1437
|
// Evidence: https://api-docs.deepseek.com/guides/responses_api/ +
|
|
1354
1438
|
// devlog/_plan/260807_deepseek_responses_streaming/000_plan.md.
|
|
1439
|
+
// Current official streams normally carry a real terminal; retain a narrow grace
|
|
1440
|
+
// repair for the historical shape that closes after a complete graph without one.
|
|
1441
|
+
modelResponsesTerminalRepair: { "deepseek-v4-flash": { graceMs: 5_000 } },
|
|
1355
1442
|
// DeepSeek's Responses route emits bare UUID item ids, which leave Codex
|
|
1356
1443
|
// clients stuck on an uncommitted turn (#938). Client-facing only — raw
|
|
1357
1444
|
// continuation snapshots keep the upstream ids.
|
|
@@ -1375,7 +1462,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1375
1462
|
// server." https://api-docs.deepseek.com/api/create-response/
|
|
1376
1463
|
statelessResponses: true,
|
|
1377
1464
|
// DeepSeek rejects a valid Codex continuation when hook-provided developer
|
|
1378
|
-
// context
|
|
1465
|
+
// context splits a call from its result (#1292); parallel calls remain one
|
|
1466
|
+
// reasoning-bearing assistant batch rather than being split per pair (#1477).
|
|
1379
1467
|
requiresAdjacentResponsesToolResults: true,
|
|
1380
1468
|
/* [Decision Log]
|
|
1381
1469
|
- 목적: DeepSeek V4 thinking mode multi-turn/tool-call requests must replay prior assistant reasoning_content.
|
|
@@ -1720,6 +1808,49 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1720
1808
|
},
|
|
1721
1809
|
note: "Authenticated first page of popular chat models only; live discovery admits at most 100 plan-available, ungated rows whose metadata explicitly reports tool use.",
|
|
1722
1810
|
},
|
|
1811
|
+
{
|
|
1812
|
+
// Primary sources checked 2026-08-08:
|
|
1813
|
+
// - https://novita.ai/docs/api-reference/model-apis-llm-create-chat-completion and
|
|
1814
|
+
// https://novita.ai/docs/api-reference/model-apis-llm-list-models document the fixed
|
|
1815
|
+
// OpenAI-compatible Chat Completions and model-list endpoints.
|
|
1816
|
+
// - https://novita.ai/docs/api-reference/basic-authentication documents Bearer API keys.
|
|
1817
|
+
// - https://novita.ai/legal/terms-of-service (updated 2026-08-05) expressly covers AI
|
|
1818
|
+
// inference APIs, third-party Model Providers, and customer Input/Output processing.
|
|
1819
|
+
// - https://huggingface.co/docs/inference-providers/main/providers/novita lists Novita as an
|
|
1820
|
+
// Inference Providers partner for chat/VLM traffic, independently supporting routing use.
|
|
1821
|
+
// - https://tsdr.uspto.gov/statusview/sn99255805 is the official use-in-commerce record
|
|
1822
|
+
// connecting the NOVITA AI mark to Hivemind Labs, Inc., a Delaware corporation. The mark
|
|
1823
|
+
// application is now abandoned; it is cited only as the public operator-identity record.
|
|
1824
|
+
// Maintainer: @olddonkey; no affiliation with Novita AI or Hivemind Labs, Inc.
|
|
1825
|
+
id: "novita",
|
|
1826
|
+
label: "Novita AI",
|
|
1827
|
+
baseUrl: "https://api.novita.ai/openai/v1",
|
|
1828
|
+
adapter: "openai-chat",
|
|
1829
|
+
authKind: "key",
|
|
1830
|
+
dashboardUrl: "https://novita.ai/settings/key-management",
|
|
1831
|
+
liveModels: true,
|
|
1832
|
+
preserveCustomDestination: true,
|
|
1833
|
+
// The live catalog is public even though the reference shows an Authorization header, so a
|
|
1834
|
+
// successful model fetch cannot prove that a supplied key is valid.
|
|
1835
|
+
apiKeyValidation: "unknown",
|
|
1836
|
+
// The request reference documents tools but not a provider-wide parallel-tool contract.
|
|
1837
|
+
parallelToolCalls: false,
|
|
1838
|
+
// Novita exposes model-specific thinking flags, not an OpenAI reasoning_effort contract.
|
|
1839
|
+
reasoningEfforts: [],
|
|
1840
|
+
modelDiscovery: {
|
|
1841
|
+
path: "models",
|
|
1842
|
+
maxResponseBytes: 512 * 1024,
|
|
1843
|
+
maxModels: 256,
|
|
1844
|
+
filter: {
|
|
1845
|
+
// Require both Novita's chat classification and the exact configured wire endpoint.
|
|
1846
|
+
allOf: [
|
|
1847
|
+
{ path: ["model_type"], equalsAny: ["chat"] },
|
|
1848
|
+
{ path: ["endpoints"], containsAny: ["chat/completions"] },
|
|
1849
|
+
],
|
|
1850
|
+
},
|
|
1851
|
+
},
|
|
1852
|
+
note: "Public live catalog filtered to rows that explicitly report chat type and Chat Completions support; key validity remains unknown until an authenticated inference request.",
|
|
1853
|
+
},
|
|
1723
1854
|
// FREEZE 2026-07-10: exact serverless ids remain auth-gated/unverified. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
1724
1855
|
{ id: "together", label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://api.together.xyz/settings/api-keys" },
|
|
1725
1856
|
{ id: "fireworks", label: "Fireworks", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
@@ -2380,6 +2511,20 @@ export function providerModelResponsesUpstreamStreaming(
|
|
|
2380
2511
|
return entry.modelResponsesUpstreamStreaming[modelId.trim().toLowerCase()];
|
|
2381
2512
|
}
|
|
2382
2513
|
|
|
2514
|
+
/** Resolve a registry-only terminal-repair policy for native Responses streams. */
|
|
2515
|
+
export function providerModelResponsesTerminalRepair(
|
|
2516
|
+
id: string,
|
|
2517
|
+
provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
|
|
2518
|
+
modelId: string,
|
|
2519
|
+
): ResponsesTerminalRepairPolicy | undefined {
|
|
2520
|
+
const entry = getProviderRegistryEntry(id);
|
|
2521
|
+
if (!entry?.modelResponsesTerminalRepair || !providerMatchesRegistryTransport(id, provider)) return undefined;
|
|
2522
|
+
const policy = entry.modelResponsesTerminalRepair[modelId.trim().toLowerCase()];
|
|
2523
|
+
const graceMs = Math.floor(policy?.graceMs ?? 0);
|
|
2524
|
+
if (!Number.isFinite(graceMs) || graceMs <= 0) return undefined;
|
|
2525
|
+
return { graceMs };
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2383
2528
|
/**
|
|
2384
2529
|
* Effective Codex account mode for a provider. For canonical `openai`, a valid persisted
|
|
2385
2530
|
* `codexAccountMode` on the provider config wins and a missing/invalid value defaults to
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
const ROUTED_CUSTOM_TOOL_PASSTHROUGH = new Set(["apply_patch"]);
|
|
2
|
+
|
|
3
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
4
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function customToolInput(argumentsText: unknown): string {
|
|
8
|
+
if (typeof argumentsText !== "string") return "";
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(argumentsText) as unknown;
|
|
11
|
+
if (isPlainObject(parsed) && typeof parsed.input === "string") return parsed.input;
|
|
12
|
+
} catch { /* malformed arguments stay visible to the client */ }
|
|
13
|
+
return argumentsText;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function customToolItemId(id: unknown): unknown {
|
|
17
|
+
if (typeof id !== "string") return id;
|
|
18
|
+
return id.startsWith("fc_") ? `ctc_${id.slice(3)}` : id;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function collectRoutedCustomToolNames(body: unknown): Set<string> {
|
|
22
|
+
const names = new Set<string>();
|
|
23
|
+
const visit = (value: unknown): void => {
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
for (const entry of value) visit(entry);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!isPlainObject(value)) return;
|
|
29
|
+
if (
|
|
30
|
+
value.type === "custom"
|
|
31
|
+
&& typeof value.name === "string"
|
|
32
|
+
&& !ROUTED_CUSTOM_TOOL_PASSTHROUGH.has(value.name)
|
|
33
|
+
) {
|
|
34
|
+
names.add(value.name);
|
|
35
|
+
}
|
|
36
|
+
for (const entry of Object.values(value)) visit(entry);
|
|
37
|
+
};
|
|
38
|
+
visit(body);
|
|
39
|
+
return names;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function collectConvertedCallIds(value: unknown, names: ReadonlySet<string>, out: Set<string>): void {
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
for (const entry of value) collectConvertedCallIds(entry, names, out);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!isPlainObject(value)) return;
|
|
48
|
+
if (
|
|
49
|
+
(value.type === "custom_tool_call" || value.type === "function_call")
|
|
50
|
+
&& typeof value.name === "string"
|
|
51
|
+
&& names.has(value.name)
|
|
52
|
+
&& typeof value.call_id === "string"
|
|
53
|
+
) {
|
|
54
|
+
out.add(value.call_id);
|
|
55
|
+
}
|
|
56
|
+
for (const entry of Object.values(value)) collectConvertedCallIds(entry, names, out);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function rewriteForUpstream(
|
|
60
|
+
value: unknown,
|
|
61
|
+
names: ReadonlySet<string>,
|
|
62
|
+
callIds: ReadonlySet<string>,
|
|
63
|
+
): unknown {
|
|
64
|
+
if (Array.isArray(value)) return value.map(entry => rewriteForUpstream(entry, names, callIds));
|
|
65
|
+
if (!isPlainObject(value)) return value;
|
|
66
|
+
|
|
67
|
+
if (value.type === "custom" && typeof value.name === "string" && names.has(value.name)) {
|
|
68
|
+
const { format: _format, ...rest } = value;
|
|
69
|
+
const isDefinition = typeof value.description === "string"
|
|
70
|
+
|| isPlainObject(value.format)
|
|
71
|
+
|| isPlainObject(value.parameters);
|
|
72
|
+
if (!isDefinition) return { ...rest, type: "function" };
|
|
73
|
+
return {
|
|
74
|
+
...rest,
|
|
75
|
+
type: "function",
|
|
76
|
+
parameters: {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
input: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "Raw input for this client-executed custom tool.",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ["input"],
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
value.type === "custom_tool_call"
|
|
92
|
+
&& typeof value.name === "string"
|
|
93
|
+
&& names.has(value.name)
|
|
94
|
+
) {
|
|
95
|
+
const { input, id: _id, ...rest } = value;
|
|
96
|
+
return {
|
|
97
|
+
...rest,
|
|
98
|
+
type: "function_call",
|
|
99
|
+
arguments: JSON.stringify({ input: typeof input === "string" ? input : "" }),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
value.type === "custom_tool_call_output"
|
|
105
|
+
&& typeof value.call_id === "string"
|
|
106
|
+
&& callIds.has(value.call_id)
|
|
107
|
+
) {
|
|
108
|
+
return { ...value, type: "function_call_output" };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let changed = false;
|
|
112
|
+
const next: Record<string, unknown> = {};
|
|
113
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
114
|
+
const rewritten = rewriteForUpstream(entry, names, callIds);
|
|
115
|
+
next[key] = rewritten;
|
|
116
|
+
changed ||= rewritten !== entry;
|
|
117
|
+
}
|
|
118
|
+
return changed ? next : value;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function rewriteRoutedCustomToolsForUpstream(body: unknown): {
|
|
122
|
+
body: unknown;
|
|
123
|
+
names: Set<string>;
|
|
124
|
+
} {
|
|
125
|
+
const names = collectRoutedCustomToolNames(body);
|
|
126
|
+
if (names.size === 0) return { body, names };
|
|
127
|
+
const callIds = new Set<string>();
|
|
128
|
+
collectConvertedCallIds(body, names, callIds);
|
|
129
|
+
return { body: rewriteForUpstream(body, names, callIds), names };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function restoreRoutedCustomCalls(
|
|
133
|
+
value: unknown,
|
|
134
|
+
names: ReadonlySet<string>,
|
|
135
|
+
): { value: unknown; changed: boolean } {
|
|
136
|
+
if (Array.isArray(value)) {
|
|
137
|
+
let changed = false;
|
|
138
|
+
const restored = value.map(entry => {
|
|
139
|
+
const result = restoreRoutedCustomCalls(entry, names);
|
|
140
|
+
changed ||= result.changed;
|
|
141
|
+
return result.value;
|
|
142
|
+
});
|
|
143
|
+
return changed ? { value: restored, changed: true } : { value, changed: false };
|
|
144
|
+
}
|
|
145
|
+
if (!isPlainObject(value)) return { value, changed: false };
|
|
146
|
+
|
|
147
|
+
let changed = false;
|
|
148
|
+
const restored: Record<string, unknown> = {};
|
|
149
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
150
|
+
const result = restoreRoutedCustomCalls(entry, names);
|
|
151
|
+
restored[key] = result.value;
|
|
152
|
+
changed ||= result.changed;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (value.type === "function_call" && typeof value.name === "string" && names.has(value.name)) {
|
|
156
|
+
restored.type = "custom_tool_call";
|
|
157
|
+
restored.id = customToolItemId(value.id);
|
|
158
|
+
restored.input = customToolInput(value.arguments);
|
|
159
|
+
delete restored.arguments;
|
|
160
|
+
changed = true;
|
|
161
|
+
}
|
|
162
|
+
return changed ? { value: restored, changed: true } : { value, changed: false };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function restoreRoutedCustomCallsInJson(
|
|
166
|
+
text: string,
|
|
167
|
+
names: ReadonlySet<string>,
|
|
168
|
+
): string {
|
|
169
|
+
if (names.size === 0) return text;
|
|
170
|
+
let payload: unknown;
|
|
171
|
+
try {
|
|
172
|
+
payload = JSON.parse(text);
|
|
173
|
+
} catch {
|
|
174
|
+
return text;
|
|
175
|
+
}
|
|
176
|
+
const restored = restoreRoutedCustomCalls(payload, names);
|
|
177
|
+
return restored.changed ? JSON.stringify(restored.value) : text;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function unwrapRoutedCustomToolArguments(argumentsText: unknown): string {
|
|
181
|
+
return customToolInput(argumentsText);
|
|
182
|
+
}
|
package/src/responses/parser.ts
CHANGED
|
@@ -324,17 +324,33 @@ export function parseRequest(body: unknown): OcxParsedRequest {
|
|
|
324
324
|
// synthetic `{type:"compaction"}` output item (src/responses/compaction.ts). Flagged for the server.
|
|
325
325
|
let compactionRequest = false;
|
|
326
326
|
let contextCompactionBoundary = false;
|
|
327
|
+
let continuationConversationMessageIndex: number | undefined;
|
|
327
328
|
|
|
328
329
|
if (typeof data.instructions === "string" && data.instructions.length > 0) {
|
|
329
330
|
systemPrompt.push(data.instructions);
|
|
330
331
|
}
|
|
331
332
|
|
|
332
333
|
if (typeof data.input === "string") {
|
|
334
|
+
if (data.previous_response_id) continuationConversationMessageIndex = messages.length;
|
|
333
335
|
messages.push({ role: "user", content: data.input, timestamp: now });
|
|
334
336
|
} else if (data.input) {
|
|
335
337
|
for (let inputIndex = 0; inputIndex < data.input.length; inputIndex++) {
|
|
336
338
|
const item = data.input[inputIndex];
|
|
337
339
|
const effectiveType = (item as { type?: string }).type ?? ("role" in item ? "message" : undefined);
|
|
340
|
+
const itemRole = (item as { role?: string }).role;
|
|
341
|
+
// Raw protocol items do not map one-to-one onto context messages. Capture the boundary while
|
|
342
|
+
// both representations are available so later metadata can stay before conversation in both.
|
|
343
|
+
if (
|
|
344
|
+
data.previous_response_id
|
|
345
|
+
&& inputIndex >= replayedInputPrefixLength
|
|
346
|
+
&& continuationConversationMessageIndex === undefined
|
|
347
|
+
&& (
|
|
348
|
+
effectiveType === "agent_message"
|
|
349
|
+
|| (effectiveType === "message" && (itemRole === "user" || itemRole === "assistant"))
|
|
350
|
+
)
|
|
351
|
+
) {
|
|
352
|
+
continuationConversationMessageIndex = messages.length;
|
|
353
|
+
}
|
|
338
354
|
|
|
339
355
|
if (effectiveType === "compaction_trigger") {
|
|
340
356
|
compactionRequest = true;
|
|
@@ -614,6 +630,9 @@ export function parseRequest(body: unknown): OcxParsedRequest {
|
|
|
614
630
|
}
|
|
615
631
|
}
|
|
616
632
|
}
|
|
633
|
+
if (data.previous_response_id && continuationConversationMessageIndex === undefined) {
|
|
634
|
+
continuationConversationMessageIndex = messages.length;
|
|
635
|
+
}
|
|
617
636
|
|
|
618
637
|
const declaredTools = buildTools(data.tools as unknown[] | undefined) ?? [];
|
|
619
638
|
const loadedTools = buildTools(loadedToolSpecs) ?? [];
|
|
@@ -683,6 +702,9 @@ export function parseRequest(body: unknown): OcxParsedRequest {
|
|
|
683
702
|
options,
|
|
684
703
|
_rawBody: body,
|
|
685
704
|
...(replayedInputPrefixLength > 0 ? { _replayPrefixLen: replayedInputPrefixLength } : {}),
|
|
705
|
+
...(continuationConversationMessageIndex !== undefined
|
|
706
|
+
? { _continuationConversationMessageIndex: continuationConversationMessageIndex }
|
|
707
|
+
: {}),
|
|
686
708
|
...(webSearch ? { _webSearch: webSearch } : {}),
|
|
687
709
|
...(imageGen ? { _imageGeneration: imageGen } : {}),
|
|
688
710
|
...(textFormat ? { _structuredOutput: true } : {}),
|