@bitkyc08/opencodex 2.7.33 → 2.7.34
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.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +21 -10
- package/README.ru.md +1 -1
- package/README.zh-CN.md +1 -1
- package/gui/dist/assets/index-BkmJJgg6.js +52 -0
- package/gui/dist/assets/index-Sg-7L_oZ.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +13 -6
- package/src/adapters/cursor/discovery.ts +39 -4
- package/src/adapters/cursor/exec-policy.ts +11 -13
- package/src/adapters/cursor/live-transport.ts +22 -4
- package/src/adapters/cursor/protobuf-events.ts +140 -8
- package/src/adapters/cursor/protobuf-request.ts +15 -0
- package/src/adapters/cursor/request-builder.ts +10 -5
- package/src/adapters/cursor/transport.ts +3 -2
- package/src/adapters/cursor/types.ts +14 -0
- package/src/adapters/kiro-constants.ts +12 -0
- package/src/adapters/kiro-errors.ts +111 -2
- package/src/adapters/kiro-events.ts +154 -35
- package/src/adapters/kiro-retry.ts +116 -32
- package/src/adapters/kiro-tools.ts +30 -20
- package/src/adapters/kiro-wire.ts +47 -6
- package/src/adapters/kiro.ts +891 -228
- package/src/adapters/openai-chat.ts +12 -5
- package/src/adapters/openai-responses.ts +7 -2
- package/src/bridge.ts +109 -26
- package/src/claude/outbound.ts +27 -4
- package/src/cli/index.ts +1 -1
- package/src/codex/catalog.ts +375 -33
- package/src/combos/index.ts +3 -0
- package/src/combos/request.ts +4 -4
- package/src/combos/resolve.ts +2 -2
- package/src/combos/types.ts +104 -2
- package/src/config.ts +70 -1
- package/src/lib/eventstream-decoder.ts +9 -0
- package/src/oauth/index.ts +3 -1
- package/src/oauth/kiro-credentials.ts +48 -20
- package/src/oauth/login-cli.ts +2 -0
- package/src/providers/derive.ts +8 -0
- package/src/providers/kiro-models.ts +2 -2
- package/src/providers/openai-sidecar.ts +28 -1
- package/src/providers/registry.ts +39 -2
- package/src/responses/parser.ts +22 -10
- package/src/responses/schema.ts +1 -0
- package/src/responses/state.ts +50 -10
- package/src/router.ts +15 -3
- package/src/server/auth-cors.ts +7 -0
- package/src/server/claude-messages.ts +6 -0
- package/src/server/index.ts +6 -3
- package/src/server/management-api.ts +187 -43
- package/src/server/ports.ts +4 -2
- package/src/server/request-log.ts +3 -2
- package/src/server/responses-item-id-repair.ts +281 -0
- package/src/server/responses.ts +274 -73
- package/src/types.ts +109 -16
- package/src/update/job.ts +81 -1
- package/src/vision/describe.ts +2 -1
- package/src/web-search/executor.ts +2 -1
- package/src/web-search/loop.ts +9 -1
- package/src/web-search/progress-stream.ts +12 -10
- package/gui/dist/assets/index-D6Fcl4yM.css +0 -1
- package/gui/dist/assets/index-d63HMU0x.js +0 -52
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ProviderAdapter } from "./base";
|
|
2
2
|
import type { AdapterEvent, OcxAssistantMessage, OcxContentPart, OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxTextContent, OcxThinkingContent, OcxToolCall, OcxUsage } from "../types";
|
|
3
3
|
import { isAllowedToolChoice, modelInList, namespacedToolName, resolveToolChoiceWireName, toolAllowedByChoice } from "../types";
|
|
4
|
-
import { mapReasoningEffort } from "../reasoning-effort";
|
|
4
|
+
import { mapReasoningEffort, modelRecordValue } from "../reasoning-effort";
|
|
5
5
|
import { redactSecretString } from "../lib/redact";
|
|
6
6
|
import { contentPartsToText } from "./image";
|
|
7
7
|
import { neutralizeIdentity } from "./identity";
|
|
@@ -460,9 +460,15 @@ function usageFromOpenAIChat(usage: Record<string, unknown> | undefined): OcxUsa
|
|
|
460
460
|
};
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
-
function
|
|
463
|
+
function resolveMaxTokens(provider: OcxProviderConfig, parsed: OcxParsedRequest): number | undefined {
|
|
464
|
+
return parsed.options.maxOutputTokens
|
|
465
|
+
?? modelRecordValue(provider.modelMaxOutputTokens, parsed.modelId)
|
|
466
|
+
?? provider.defaultMaxOutputTokens;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function thinkingBudgetForEffort(parsed: OcxParsedRequest, reasoningEffort: string, maxOutputTokens?: number): number | undefined {
|
|
464
470
|
if (parsed.options.reasoning === "minimal") return 0;
|
|
465
|
-
const maxBudget =
|
|
471
|
+
const maxBudget = maxOutputTokens ?? 32768;
|
|
466
472
|
const fractions: Record<string, number> = {
|
|
467
473
|
low: 0.20,
|
|
468
474
|
medium: 0.50,
|
|
@@ -495,6 +501,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
495
501
|
messages,
|
|
496
502
|
stream: parsed.stream,
|
|
497
503
|
};
|
|
504
|
+
const maxTokens = resolveMaxTokens(provider, parsed);
|
|
498
505
|
const openRouterRouting = resolveOpenRouterRouting(provider, parsed.modelId);
|
|
499
506
|
if (openRouterRouting) body.provider = openRouterProviderPayload(openRouterRouting);
|
|
500
507
|
if (tools) body.tools = tools;
|
|
@@ -503,7 +510,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
503
510
|
? (toolChoice === "none" ? "none" : "auto")
|
|
504
511
|
: toolChoice;
|
|
505
512
|
}
|
|
506
|
-
if (
|
|
513
|
+
if (maxTokens !== undefined) body.max_tokens = maxTokens;
|
|
507
514
|
if (parsed.options.temperature !== undefined && !modelInList(provider.noTemperatureModels, parsed.modelId)) {
|
|
508
515
|
body.temperature = parsed.options.temperature;
|
|
509
516
|
}
|
|
@@ -514,7 +521,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
514
521
|
const reasoningEffort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
515
522
|
if (reasoningEffort !== undefined) {
|
|
516
523
|
if (modelInList(provider.thinkingBudgetModels, parsed.modelId)) {
|
|
517
|
-
const budget = thinkingBudgetForEffort(parsed, reasoningEffort);
|
|
524
|
+
const budget = thinkingBudgetForEffort(parsed, reasoningEffort, maxTokens);
|
|
518
525
|
if (budget !== undefined) body.thinking_budget = budget;
|
|
519
526
|
} else if (modelInList(provider.thinkingToggleModels, parsed.modelId)) {
|
|
520
527
|
// Vendor thinking-toggle wire (MiMo v2.x, GLM 5/5.1): the mapped value is the toggle
|
|
@@ -440,8 +440,13 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
440
440
|
headers["chatgpt-account-id"] = override.chatgptAccountId;
|
|
441
441
|
}
|
|
442
442
|
} else {
|
|
443
|
-
|
|
444
|
-
|
|
443
|
+
if (provider.responsesPath === undefined) {
|
|
444
|
+
const base = provider.baseUrl.replace(/\/v1\/?$/, "");
|
|
445
|
+
url = `${base}/v1/responses`;
|
|
446
|
+
} else {
|
|
447
|
+
const base = provider.baseUrl.replace(/\/$/, "");
|
|
448
|
+
url = `${base}${provider.responsesPath}`;
|
|
449
|
+
}
|
|
445
450
|
if (provider.apiKey) headers["Authorization"] = `Bearer ${provider.apiKey}`;
|
|
446
451
|
if (provider.headers) Object.assign(headers, provider.headers);
|
|
447
452
|
}
|
package/src/bridge.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AdapterEvent, OcxUsage } from "./types";
|
|
1
|
+
import type { AdapterEvent, OcxMessagePhase, OcxProviderContinuationState, OcxUsage } from "./types";
|
|
2
2
|
import { adapterFailureFromMessage, classifyError, type OcxErrorPayload } from "./lib/errors";
|
|
3
3
|
import { encodeCompactionSummary } from "./responses/compaction";
|
|
4
4
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
|
|
@@ -43,6 +43,18 @@ function responseError(status: number, type: string, message: string): OcxErrorP
|
|
|
43
43
|
return classifyError(status, type, message);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>): { httpStatus: number; error: OcxErrorPayload } {
|
|
47
|
+
if (event.status === undefined && event.errorType === undefined && event.code === undefined) {
|
|
48
|
+
return adapterFailureFromMessage(event.message);
|
|
49
|
+
}
|
|
50
|
+
const fallback = adapterFailureFromMessage(event.message);
|
|
51
|
+
const httpStatus = event.status ?? fallback.httpStatus;
|
|
52
|
+
const error = classifyError(httpStatus, event.errorType ?? fallback.error.type, event.message);
|
|
53
|
+
if (event.errorType !== undefined) error.type = event.errorType;
|
|
54
|
+
if (event.code !== undefined) error.code = event.code;
|
|
55
|
+
return { httpStatus, error };
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
export { adapterFailureFromMessage } from "./lib/errors";
|
|
47
59
|
|
|
48
60
|
/**
|
|
@@ -85,7 +97,7 @@ export function bridgeToResponsesSSE(
|
|
|
85
97
|
/** One-shot: first non-empty text/thinking/raw-reasoning delta observed (WP4 TTFT). */
|
|
86
98
|
onFirstOutput?: () => void;
|
|
87
99
|
onTerminal?: (status: ResponsesTerminalStatus) => void;
|
|
88
|
-
onCompletedResponse?: (response: Record<string, unknown
|
|
100
|
+
onCompletedResponse?: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) => void;
|
|
89
101
|
},
|
|
90
102
|
): ReadableStream<Uint8Array> {
|
|
91
103
|
// Freeform/custom tools (apply_patch) carry their body in `input`; the model is given a
|
|
@@ -173,9 +185,10 @@ export function bridgeToResponsesSSE(
|
|
|
173
185
|
let outputIndex = 0;
|
|
174
186
|
const finishedItems: OutputItem[] = [];
|
|
175
187
|
|
|
176
|
-
const responseSnapshot = (status: string, output: OutputItem[]) => ({
|
|
188
|
+
const responseSnapshot = (status: string, output: OutputItem[], endTurn?: boolean) => ({
|
|
177
189
|
id: responseId, object: "response", created_at: createdAt,
|
|
178
190
|
status, model: modelId, output, usage: null,
|
|
191
|
+
...(endTurn !== undefined ? { end_turn: endTurn } : {}),
|
|
179
192
|
});
|
|
180
193
|
|
|
181
194
|
emit("response.created", { response: responseSnapshot("in_progress", []) });
|
|
@@ -213,7 +226,7 @@ export function bridgeToResponsesSSE(
|
|
|
213
226
|
try { controller.enqueue(heartbeatFrame); } catch { closed = true; }
|
|
214
227
|
}, heartbeatMs);
|
|
215
228
|
|
|
216
|
-
let currentMsg: { itemId: string; outputIndex: number; text: string } | null = null;
|
|
229
|
+
let currentMsg: { itemId: string; outputIndex: number; text: string; phase?: OcxMessagePhase } | null = null;
|
|
217
230
|
let currentReasoning: { itemId: string; outputIndex: number; text: string } | null = null;
|
|
218
231
|
let currentRawReasoning: { itemId: string; outputIndex: number; text: string } | null = null;
|
|
219
232
|
// Anthropic extended-thinking round-trip state: the signature signs the CURRENT thinking
|
|
@@ -299,6 +312,7 @@ export function bridgeToResponsesSSE(
|
|
|
299
312
|
const item = {
|
|
300
313
|
type: "message", id: currentMsg.itemId, status: "completed", role: "assistant",
|
|
301
314
|
content: [{ type: "output_text", text: currentMsg.text, annotations }],
|
|
315
|
+
...(currentMsg.phase ? { phase: currentMsg.phase } : {}),
|
|
302
316
|
};
|
|
303
317
|
emit("response.output_item.done", { output_index: currentMsg.outputIndex, item });
|
|
304
318
|
finishedItems.push(item as OutputItem);
|
|
@@ -441,7 +455,7 @@ export function bridgeToResponsesSSE(
|
|
|
441
455
|
// its compaction UI renders nothing mid-turn, so nothing is lost visually.
|
|
442
456
|
if (options?.compaction) {
|
|
443
457
|
if (event.type === "text_delta") { compactionText += event.text; continue; }
|
|
444
|
-
if (event.type !== "done" && event.type !== "error") continue;
|
|
458
|
+
if (event.type !== "done" && event.type !== "incomplete" && event.type !== "error") continue;
|
|
445
459
|
}
|
|
446
460
|
switch (event.type) {
|
|
447
461
|
case "text_delta": {
|
|
@@ -449,18 +463,20 @@ export function bridgeToResponsesSSE(
|
|
|
449
463
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
450
464
|
flushHiddenRawReasoning();
|
|
451
465
|
if (currentToolCall) closeCurrentToolCall();
|
|
466
|
+
if (currentMsg && currentMsg.phase !== event.phase) closeCurrentMessage();
|
|
452
467
|
if (!currentMsg) {
|
|
453
468
|
const itemId = `msg_${uuid()}`;
|
|
454
469
|
const item = {
|
|
455
470
|
type: "message", id: itemId, status: "in_progress", role: "assistant",
|
|
456
471
|
content: [] as { type: string; text: string; annotations: never[] }[],
|
|
472
|
+
...(event.phase ? { phase: event.phase } : {}),
|
|
457
473
|
};
|
|
458
474
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
459
475
|
emit("response.content_part.added", {
|
|
460
476
|
item_id: itemId, output_index: outputIndex, content_index: 0,
|
|
461
477
|
part: { type: "output_text", text: "", annotations: [] },
|
|
462
478
|
});
|
|
463
|
-
currentMsg = { itemId, outputIndex, text: "" };
|
|
479
|
+
currentMsg = { itemId, outputIndex, text: "", ...(event.phase ? { phase: event.phase } : {}) };
|
|
464
480
|
}
|
|
465
481
|
currentMsg.text += event.text;
|
|
466
482
|
emit("response.output_text.delta", {
|
|
@@ -634,17 +650,49 @@ export function bridgeToResponsesSSE(
|
|
|
634
650
|
finishedItems.push(item as OutputItem);
|
|
635
651
|
outputIndex++;
|
|
636
652
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
653
|
+
if (event.stopReason === "max_tokens") {
|
|
654
|
+
// Upstream hit its output token cap. Surface as incomplete so the client can
|
|
655
|
+
// distinguish a truncated turn from a genuinely finished one (issue #246).
|
|
656
|
+
const response = {
|
|
657
|
+
...responseSnapshot("incomplete", finishedItems, event.endTurn),
|
|
658
|
+
usage: responsesUsage(event.usage),
|
|
659
|
+
incomplete_details: { reason: "max_output_tokens" },
|
|
660
|
+
};
|
|
661
|
+
// Still cache the partial output so previous_response_id replay works.
|
|
662
|
+
options?.onCompletedResponse?.(response, event.providerState);
|
|
663
|
+
emit("response.incomplete", { response });
|
|
664
|
+
reportTerminal("incomplete");
|
|
665
|
+
} else {
|
|
666
|
+
const response = { ...responseSnapshot("completed", finishedItems, event.endTurn), usage: responsesUsage(event.usage) };
|
|
667
|
+
options?.onCompletedResponse?.(response, event.providerState);
|
|
668
|
+
emit("response.completed", {
|
|
669
|
+
response,
|
|
670
|
+
});
|
|
671
|
+
reportTerminal("completed");
|
|
672
|
+
}
|
|
673
|
+
terminated = true;
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
case "incomplete": {
|
|
677
|
+
if (currentMsg) closeCurrentMessage();
|
|
678
|
+
if (currentReasoning) closeCurrentReasoning();
|
|
679
|
+
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
680
|
+
flushHiddenRawReasoning();
|
|
681
|
+
if (currentToolCall) closeCurrentToolCall();
|
|
682
|
+
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
683
|
+
flushHiddenReasoningEnvelope();
|
|
684
|
+
emit("response.incomplete", {
|
|
685
|
+
response: {
|
|
686
|
+
...responseSnapshot("incomplete", finishedItems, event.endTurn),
|
|
687
|
+
usage: responsesUsage(event.usage),
|
|
688
|
+
incomplete_details: {
|
|
689
|
+
reason: event.reason,
|
|
690
|
+
...(event.message ? { message: event.message } : {}),
|
|
691
|
+
...(event.retryable !== undefined ? { retryable: event.retryable } : {}),
|
|
692
|
+
},
|
|
693
|
+
},
|
|
646
694
|
});
|
|
647
|
-
reportTerminal(
|
|
695
|
+
reportTerminal("incomplete");
|
|
648
696
|
terminated = true;
|
|
649
697
|
break;
|
|
650
698
|
}
|
|
@@ -655,7 +703,7 @@ export function bridgeToResponsesSSE(
|
|
|
655
703
|
flushHiddenRawReasoning();
|
|
656
704
|
if (currentToolCall) closeCurrentToolCall();
|
|
657
705
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
658
|
-
const failure =
|
|
706
|
+
const failure = adapterFailureFromEvent(event);
|
|
659
707
|
emit("response.failed", {
|
|
660
708
|
response: {
|
|
661
709
|
...responseSnapshot("failed", finishedItems),
|
|
@@ -664,6 +712,7 @@ export function bridgeToResponsesSSE(
|
|
|
664
712
|
...(event.usage ? { usage: responsesUsage(event.usage) } : {}),
|
|
665
713
|
error: failure.error,
|
|
666
714
|
last_error: failure.error,
|
|
715
|
+
...(event.retryable !== undefined ? { retryable: event.retryable } : {}),
|
|
667
716
|
},
|
|
668
717
|
});
|
|
669
718
|
reportTerminal("failed");
|
|
@@ -736,16 +785,20 @@ export function buildResponseJSON(
|
|
|
736
785
|
toolSearchToolNames?: Set<string>;
|
|
737
786
|
/** Remote compaction v2 turn — append one synthetic compaction output item (see bridgeToResponsesSSE). */
|
|
738
787
|
compaction?: boolean;
|
|
788
|
+
onProviderState?: (state: OcxProviderContinuationState) => void;
|
|
739
789
|
},
|
|
740
790
|
): Record<string, unknown> {
|
|
741
791
|
const responseId = `resp_${uuid()}`;
|
|
742
792
|
const output: OutputItem[] = [];
|
|
743
793
|
let usage: OcxUsage | undefined;
|
|
744
|
-
let
|
|
794
|
+
let errorEvent: Extract<AdapterEvent, { type: "error" }> | undefined;
|
|
795
|
+
let incompleteEvent: Extract<AdapterEvent, { type: "incomplete" }> | undefined;
|
|
796
|
+
let endTurn: boolean | undefined;
|
|
745
797
|
let stopReason: string | undefined;
|
|
746
798
|
let compactionText = "";
|
|
747
799
|
|
|
748
800
|
let currentText = "";
|
|
801
|
+
let currentTextPhase: OcxMessagePhase | undefined;
|
|
749
802
|
let currentSummaryReasoning = "";
|
|
750
803
|
let currentRawReasoning = "";
|
|
751
804
|
// Anthropic extended-thinking round-trip (batch): see bridgeToResponsesSSE counterpart.
|
|
@@ -774,8 +827,10 @@ export function buildResponseJSON(
|
|
|
774
827
|
output.push({
|
|
775
828
|
type: "message", id: `msg_${uuid()}`, role: "assistant", status: "completed",
|
|
776
829
|
content: [{ type: "output_text", text: currentText, annotations }],
|
|
830
|
+
...(currentTextPhase ? { phase: currentTextPhase } : {}),
|
|
777
831
|
});
|
|
778
832
|
currentText = "";
|
|
833
|
+
currentTextPhase = undefined;
|
|
779
834
|
};
|
|
780
835
|
const flushSummaryReasoning = () => {
|
|
781
836
|
if (!currentSummaryReasoning && !batchSignature && batchRedacted.length === 0) return;
|
|
@@ -847,13 +902,17 @@ export function buildResponseJSON(
|
|
|
847
902
|
for (const e of events) {
|
|
848
903
|
switch (e.type) {
|
|
849
904
|
case "text_delta":
|
|
905
|
+
if (currentText && currentTextPhase !== e.phase) flushText();
|
|
850
906
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
851
907
|
if (currentRawReasoning) flushRawReasoning();
|
|
852
908
|
if (currentToolCallId) flushToolCall();
|
|
853
909
|
// Compaction turns keep the summary out of normal message output (replay dedup — see
|
|
854
910
|
// bridgeToResponsesSSE); it ships only inside the synthetic compaction item below.
|
|
855
911
|
if (options?.compaction) compactionText += e.text;
|
|
856
|
-
else
|
|
912
|
+
else {
|
|
913
|
+
currentTextPhase = e.phase;
|
|
914
|
+
currentText += e.text;
|
|
915
|
+
}
|
|
857
916
|
break;
|
|
858
917
|
case "thinking_delta":
|
|
859
918
|
if (currentText) flushText();
|
|
@@ -913,11 +972,19 @@ export function buildResponseJSON(
|
|
|
913
972
|
}
|
|
914
973
|
break;
|
|
915
974
|
case "error":
|
|
916
|
-
|
|
975
|
+
errorEvent = e;
|
|
976
|
+
usage = e.usage ?? usage;
|
|
977
|
+
break;
|
|
978
|
+
case "incomplete":
|
|
979
|
+
incompleteEvent = e;
|
|
980
|
+
endTurn = e.endTurn;
|
|
981
|
+
if (e.providerState) options?.onProviderState?.(e.providerState);
|
|
917
982
|
break;
|
|
918
983
|
case "done":
|
|
919
984
|
usage = e.usage;
|
|
920
|
-
|
|
985
|
+
endTurn = e.endTurn;
|
|
986
|
+
if (e.providerState) options?.onProviderState?.(e.providerState);
|
|
987
|
+
if (e.stopReason === "max_tokens") stopReason = "max_tokens";
|
|
921
988
|
break;
|
|
922
989
|
}
|
|
923
990
|
}
|
|
@@ -925,18 +992,34 @@ export function buildResponseJSON(
|
|
|
925
992
|
flushSummaryReasoning();
|
|
926
993
|
flushRawReasoning();
|
|
927
994
|
flushToolCall();
|
|
928
|
-
if (options?.compaction && !
|
|
995
|
+
if (options?.compaction && !errorEvent) {
|
|
929
996
|
output.push({ type: "compaction", id: `cmp_${uuid()}`, encrypted_content: encodeCompactionSummary(compactionText) });
|
|
930
997
|
}
|
|
931
998
|
|
|
999
|
+
const failure = errorEvent ? adapterFailureFromEvent(errorEvent) : undefined;
|
|
1000
|
+
const status = errorEvent
|
|
1001
|
+
? "failed"
|
|
1002
|
+
: incompleteEvent || stopReason === "max_tokens"
|
|
1003
|
+
? "incomplete"
|
|
1004
|
+
: "completed";
|
|
932
1005
|
return {
|
|
933
1006
|
id: responseId, object: "response",
|
|
934
1007
|
created_at: Math.floor(Date.now() / 1000),
|
|
935
|
-
status
|
|
1008
|
+
status,
|
|
936
1009
|
model: modelId, output,
|
|
937
|
-
...(
|
|
938
|
-
...(
|
|
939
|
-
|
|
1010
|
+
...(endTurn !== undefined ? { end_turn: endTurn } : {}),
|
|
1011
|
+
...(failure ? { error: failure.error, last_error: failure.error } : {}),
|
|
1012
|
+
...(errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}),
|
|
1013
|
+
...(incompleteEvent ? {
|
|
1014
|
+
incomplete_details: {
|
|
1015
|
+
reason: incompleteEvent.reason,
|
|
1016
|
+
...(incompleteEvent.message ? { message: incompleteEvent.message } : {}),
|
|
1017
|
+
...(incompleteEvent.retryable !== undefined ? { retryable: incompleteEvent.retryable } : {}),
|
|
1018
|
+
},
|
|
1019
|
+
} : stopReason === "max_tokens" ? {
|
|
1020
|
+
incomplete_details: { reason: "max_output_tokens" },
|
|
1021
|
+
} : {}),
|
|
1022
|
+
usage: responsesUsage(incompleteEvent?.usage ?? usage),
|
|
940
1023
|
};
|
|
941
1024
|
}
|
|
942
1025
|
|
package/src/claude/outbound.ts
CHANGED
|
@@ -314,16 +314,26 @@ export function responsesSseToAnthropicSse(
|
|
|
314
314
|
}
|
|
315
315
|
case "response.completed": {
|
|
316
316
|
const response = isRec(data.response) ? data.response : {};
|
|
317
|
+
if (response.end_turn === false && !sawToolUse) {
|
|
318
|
+
fail(529, "upstream turn ended without a final answer", true);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
317
321
|
finish(sawToolUse ? "tool_use" : "end_turn", response.usage);
|
|
318
322
|
break;
|
|
319
323
|
}
|
|
320
324
|
case "response.incomplete": {
|
|
321
325
|
const response = isRec(data.response) ? data.response : {};
|
|
322
326
|
const details = isRec(response.incomplete_details) ? response.incomplete_details : {};
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
+
if (details.reason === "max_output_tokens") {
|
|
328
|
+
finish("max_tokens", response.usage);
|
|
329
|
+
} else if (details.reason === "content_filter") {
|
|
330
|
+
finish("refusal", response.usage);
|
|
331
|
+
} else {
|
|
332
|
+
const message = typeof details.message === "string" && details.message.trim()
|
|
333
|
+
? details.message
|
|
334
|
+
: `upstream response was incomplete${typeof details.reason === "string" ? ` (${details.reason})` : ""}`;
|
|
335
|
+
fail(529, message, true);
|
|
336
|
+
}
|
|
327
337
|
break;
|
|
328
338
|
}
|
|
329
339
|
case "response.failed": {
|
|
@@ -454,8 +464,21 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string): R
|
|
|
454
464
|
}
|
|
455
465
|
|
|
456
466
|
const details = isRec(body.incomplete_details) ? body.incomplete_details : {};
|
|
467
|
+
if (body.status === "incomplete"
|
|
468
|
+
&& details.reason !== "max_output_tokens"
|
|
469
|
+
&& details.reason !== "content_filter") {
|
|
470
|
+
const message = typeof details.message === "string" && details.message.trim()
|
|
471
|
+
? details.message
|
|
472
|
+
: `upstream response was incomplete${typeof details.reason === "string" ? ` (${details.reason})` : ""}`;
|
|
473
|
+
return anthropicErrorBody(529, message, "overloaded_error");
|
|
474
|
+
}
|
|
475
|
+
if (body.status === "completed" && body.end_turn === false && !sawToolUse) {
|
|
476
|
+
return anthropicErrorBody(529, "upstream turn ended without a final answer", "overloaded_error");
|
|
477
|
+
}
|
|
457
478
|
const stopReason = body.status === "incomplete" && details.reason === "max_output_tokens"
|
|
458
479
|
? "max_tokens"
|
|
480
|
+
: body.status === "incomplete" && details.reason === "content_filter"
|
|
481
|
+
? "refusal"
|
|
459
482
|
: sawToolUse ? "tool_use" : "end_turn";
|
|
460
483
|
|
|
461
484
|
return {
|
package/src/cli/index.ts
CHANGED
|
@@ -106,7 +106,7 @@ async function chooseListenPort(requestedPort?: number): Promise<number> {
|
|
|
106
106
|
preferRetryIntervalMs: 50,
|
|
107
107
|
allowEphemeralFallback: !hardPin,
|
|
108
108
|
});
|
|
109
|
-
if (selected !== preferred) {
|
|
109
|
+
if (preferred > 0 && selected !== preferred) {
|
|
110
110
|
console.log(`⚠️ Port ${preferred} is busy; starting opencodex on ${selected}.`);
|
|
111
111
|
}
|
|
112
112
|
if (shouldPersistSelectedPort(config.port, selected, preferred)) {
|