@golba98/codexa 1.0.3 → 1.0.5
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 +17 -18
- package/bin/codexa.js +62 -144
- package/package.json +4 -3
- package/src/app.tsx +642 -303
- package/src/commands/handler.ts +7 -18
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +1 -1
- package/src/config/trustStore.ts +1 -1
- package/src/core/README.md +52 -0
- package/src/core/agent/loop.ts +282 -0
- package/src/core/agent/protocol.ts +211 -0
- package/src/core/agent/tools.ts +414 -0
- package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
- package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
- package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
- package/src/core/debug/modelStateDebug.ts +34 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/executableResolver.ts +5 -1
- package/src/core/models/codexModelCapabilities.ts +57 -4
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/providerModelCache.ts +106 -0
- package/src/core/perf/renderDebug.ts +10 -6
- package/src/core/process/CommandRunner.ts +12 -1
- package/src/core/providerLauncher/registry.ts +64 -18
- package/src/core/providerLauncher/types.ts +12 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +10 -6
- package/src/core/providerRuntime/antigravity.ts +461 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -446
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/contextMetadata.ts +12 -24
- package/src/core/providerRuntime/local.ts +129 -51
- package/src/core/providerRuntime/mistralVibe.ts +663 -0
- package/src/core/providerRuntime/models.ts +40 -15
- package/src/core/providerRuntime/reasoning.ts +9 -6
- package/src/core/providerRuntime/registry.ts +76 -4
- package/src/core/providerRuntime/types.ts +20 -14
- package/src/core/providers/codexSubprocess.ts +2 -2
- package/src/core/providers/types.ts +1 -1
- package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
- package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
- package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
- package/src/core/terminal/clearFrameBoundary.ts +814 -0
- package/src/core/terminal/frameLock.ts +109 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/terminalControl.ts +22 -0
- package/src/core/terminal/terminalTitle.ts +16 -102
- package/src/core/{channel.ts → version/channel.ts} +1 -1
- package/src/core/{updateCheck.ts → version/updateCheck.ts} +10 -5
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +9 -16
- package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
- package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
- package/src/headless/execRunner.ts +2 -2
- package/src/index.tsx +66 -98
- package/src/session/appSession.ts +10 -7
- package/src/session/chatLifecycle.ts +1 -1
- package/src/session/liveRenderScheduler.ts +1 -1
- package/src/session/types.ts +1 -1
- package/src/test/runtimeTestUtils.ts +90 -0
- package/src/ui/{ActivityBars.tsx → chrome/ActivityBars.tsx} +1 -1
- package/src/ui/{ActivityIndicator.tsx → chrome/ActivityIndicator.tsx} +3 -3
- package/src/ui/{AnimatedStatusText.tsx → chrome/AnimatedStatusText.tsx} +3 -3
- package/src/ui/chrome/AppShell.tsx +672 -0
- package/src/ui/{BottomComposer.tsx → chrome/BottomComposer.tsx} +159 -158
- package/src/ui/{DashCard.tsx → chrome/DashCard.tsx} +2 -2
- package/src/ui/{RunFooter.tsx → chrome/RunFooter.tsx} +3 -3
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/{Spinner.tsx → chrome/Spinner.tsx} +1 -1
- package/src/ui/{TopHeader.tsx → chrome/TopHeader.tsx} +54 -38
- package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +5 -5
- package/src/ui/{commandNormalize.ts → input/commandNormalize.ts} +1 -1
- package/src/ui/{focus.ts → input/focus.ts} +1 -1
- package/src/ui/{inputBuffer.ts → input/inputBuffer.ts} +3 -3
- package/src/ui/layout.ts +298 -24
- package/src/ui/{AttachmentImportPanel.tsx → panels/AttachmentImportPanel.tsx} +3 -3
- package/src/ui/{AuthPanel.tsx → panels/AuthPanel.tsx} +5 -5
- package/src/ui/{BackendPicker.tsx → panels/BackendPicker.tsx} +2 -2
- package/src/ui/{ModePicker.tsx → panels/ModePicker.tsx} +2 -2
- package/src/ui/{ModelPicker.tsx → panels/ModelPicker.tsx} +2 -2
- package/src/ui/{ModelPickerScreen.tsx → panels/ModelPickerScreen.tsx} +224 -42
- package/src/ui/{ModelReasoningPicker.tsx → panels/ModelReasoningPicker.tsx} +5 -5
- package/src/ui/{Panel.tsx → panels/Panel.tsx} +1 -1
- package/src/ui/{PermissionsPanel.tsx → panels/PermissionsPanel.tsx} +3 -3
- package/src/ui/{PlanActionPicker.tsx → panels/PlanActionPicker.tsx} +2 -2
- package/src/ui/{PlanReviewPanel.tsx → panels/PlanReviewPanel.tsx} +5 -5
- package/src/ui/panels/ProviderPicker.tsx +737 -0
- package/src/ui/{ReasoningPicker.tsx → panels/ReasoningPicker.tsx} +3 -3
- package/src/ui/{SelectionPanel.tsx → panels/SelectionPanel.tsx} +5 -1
- package/src/ui/{SettingsPanel.tsx → panels/SettingsPanel.tsx} +4 -4
- package/src/ui/{TextEntryPanel.tsx → panels/TextEntryPanel.tsx} +4 -4
- package/src/ui/{ThemePicker.tsx → panels/ThemePicker.tsx} +2 -2
- package/src/ui/{UpdatePromptPanel.tsx → panels/UpdatePromptPanel.tsx} +17 -23
- package/src/ui/{Markdown.tsx → render/Markdown.tsx} +2 -2
- package/src/ui/{diffRenderer.ts → render/diffRenderer.ts} +1 -1
- package/src/ui/{logoVariants.ts → render/logoVariants.ts} +4 -8
- package/src/ui/{modeDisplay.ts → render/modeDisplay.ts} +2 -2
- package/src/ui/{outputPipeline.ts → render/outputPipeline.ts} +2 -2
- package/src/ui/{runtimeDisplay.ts → render/runtimeDisplay.ts} +23 -10
- package/src/ui/{textLayout.ts → render/textLayout.ts} +15 -4
- package/src/ui/{ActionRequiredBlock.tsx → timeline/ActionRequiredBlock.tsx} +3 -3
- package/src/ui/{AgentBlock.tsx → timeline/AgentBlock.tsx} +10 -10
- package/src/ui/{StaticIntroItem.tsx → timeline/StaticIntroItem.tsx} +2 -2
- package/src/ui/{ThinkingBlock.tsx → timeline/ThinkingBlock.tsx} +4 -4
- package/src/ui/{Timeline.tsx → timeline/Timeline.tsx} +1620 -1471
- package/src/ui/timeline/TranscriptShell.tsx +322 -0
- package/src/ui/{TurnGroup.tsx → timeline/TurnGroup.tsx} +15 -15
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/{progressEntries.ts → timeline/progressEntries.ts} +3 -3
- package/src/ui/{runActivityView.ts → timeline/runActivityView.ts} +1 -1
- package/src/ui/{timelineMeasure.ts → timeline/timelineMeasure.ts} +206 -134
- package/src/core/codex.ts +0 -124
- package/src/ui/AppShell.tsx +0 -706
- package/src/ui/ProviderPicker.tsx +0 -321
- package/src/ui/StaticTranscriptItem.tsx +0 -56
- /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
- /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
- /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
- /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
- /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
- /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
- /package/src/ui/{busyStatusAnimation.ts → chrome/busyStatusAnimation.ts} +0 -0
- /package/src/ui/{slashCommands.ts → input/slashCommands.ts} +0 -0
- /package/src/ui/{terminalAnswerFormat.ts → render/terminalAnswerFormat.ts} +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { resolveClaudeExecutable } from "../executables/claudeExecutable.js";
|
|
2
|
+
import { runCommand } from "../process/CommandRunner.js";
|
|
3
|
+
import {
|
|
4
|
+
claudeCodeModelsToProviderModels,
|
|
5
|
+
discoverClaudeCodeCapabilities,
|
|
6
|
+
discoverModelsFromClaudePackageMetadata,
|
|
7
|
+
} from "./claudeCodeDiscovery.js";
|
|
8
|
+
|
|
9
|
+
async function main(): Promise<void> {
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const resolvedCommand = await resolveClaudeExecutable({ cwd });
|
|
12
|
+
const versionResult = await runCommand({
|
|
13
|
+
executable: resolvedCommand,
|
|
14
|
+
args: ["--version"],
|
|
15
|
+
cwd,
|
|
16
|
+
timeoutMs: 5_000,
|
|
17
|
+
}).result;
|
|
18
|
+
const packageMetadata = discoverModelsFromClaudePackageMetadata(resolvedCommand);
|
|
19
|
+
const discovery = await discoverClaudeCodeCapabilities({ cwd });
|
|
20
|
+
const normalized = claudeCodeModelsToProviderModels(discovery.models);
|
|
21
|
+
|
|
22
|
+
const report = {
|
|
23
|
+
claudeCommand: resolvedCommand,
|
|
24
|
+
claudeBinaryPath: packageMetadata?.sourcePath ?? resolvedCommand,
|
|
25
|
+
claudeVersion: versionResult.status === "completed" && versionResult.exitCode === 0
|
|
26
|
+
? versionResult.stdout.trim()
|
|
27
|
+
: null,
|
|
28
|
+
discoverySourceUsed: discovery.modelSource,
|
|
29
|
+
packageMetadataSource: packageMetadata?.sourcePath ?? null,
|
|
30
|
+
rawDiscoveredModelEntries: packageMetadata?.rawModelIds ?? [],
|
|
31
|
+
normalizedModelEntries: normalized.map((model) => ({
|
|
32
|
+
id: model.modelId,
|
|
33
|
+
label: model.label,
|
|
34
|
+
family: model.family,
|
|
35
|
+
version: model.version,
|
|
36
|
+
canonicalId: model.canonicalId,
|
|
37
|
+
source: model.source,
|
|
38
|
+
isFallback: model.isFallback,
|
|
39
|
+
discoveryKind: model.discoveryKind,
|
|
40
|
+
})),
|
|
41
|
+
fallbackReason: discovery.modelSource === "fallback"
|
|
42
|
+
? discovery.diagnostics ?? { reason: "No Claude Code command, package metadata, settings, or config model source returned versioned models." }
|
|
43
|
+
: null,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await main();
|
|
51
|
+
} catch (err) {
|
|
52
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53
|
+
process.stderr.write(`debug:claude-models failed: ${message}\n`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
@@ -67,14 +67,9 @@ const KNOWN_CONTEXT_REGISTRY: Record<string, KnownContextRegistryEntry> = {
|
|
|
67
67
|
// Exact documented Anthropic model IDs only. Provider aliases such as
|
|
68
68
|
// "haiku" remain unknown unless configured or discovered by CLI metadata.
|
|
69
69
|
// Source: https://docs.anthropic.com/en/docs/about-claude/models
|
|
70
|
-
"anthropic:claude-
|
|
71
|
-
contextLength: 200_000,
|
|
72
|
-
sourceUrl: "https://docs.anthropic.com/en/docs/about-claude/models",
|
|
73
|
-
note: "Anthropic documented context window for this exact model ID.",
|
|
74
|
-
},
|
|
75
|
-
"anthropic:claude-sonnet-4-6": {
|
|
76
|
-
contextLength: 200_000,
|
|
77
|
-
sourceUrl: "https://docs.anthropic.com/en/docs/about-claude/models",
|
|
70
|
+
"anthropic:claude-sonnet-4-6": {
|
|
71
|
+
contextLength: 200_000,
|
|
72
|
+
sourceUrl: "https://docs.anthropic.com/en/docs/about-claude/models",
|
|
78
73
|
note: "Anthropic documented context window for this exact model ID.",
|
|
79
74
|
},
|
|
80
75
|
"anthropic:claude-haiku-4-5": {
|
|
@@ -149,16 +144,9 @@ const KNOWN_CONTEXT_REGISTRY: Record<string, KnownContextRegistryEntry> = {
|
|
|
149
144
|
},
|
|
150
145
|
};
|
|
151
146
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
sonnet: "claude-sonnet-4-6",
|
|
156
|
-
haiku: "claude-haiku-4-5",
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
// Normalise OpenAI/Codex model IDs to lowercase-dashed form before registry lookup.
|
|
160
|
-
// Handles display-label variants like "GPT-5 Codex" → "gpt-5-codex".
|
|
161
|
-
function normalizeOpenAIModelId(modelId: string): string {
|
|
147
|
+
// Normalise OpenAI/Codex model IDs to lowercase-dashed form before registry lookup.
|
|
148
|
+
// Handles display-label variants like "GPT-5 Codex" → "gpt-5-codex".
|
|
149
|
+
function normalizeOpenAIModelId(modelId: string): string {
|
|
162
150
|
return modelId.toLowerCase().replace(/\s+/g, "-");
|
|
163
151
|
}
|
|
164
152
|
|
|
@@ -333,12 +321,12 @@ function resolveFromConfig(
|
|
|
333
321
|
};
|
|
334
322
|
}
|
|
335
323
|
|
|
336
|
-
function resolveFromKnownRegistry(providerId: ProviderId, modelId: string): ModelContextMetadata | null {
|
|
337
|
-
const lookupId = providerId === "anthropic"
|
|
338
|
-
?
|
|
339
|
-
: providerId === "openai"
|
|
340
|
-
? normalizeOpenAIModelId(modelId)
|
|
341
|
-
: modelId;
|
|
324
|
+
function resolveFromKnownRegistry(providerId: ProviderId, modelId: string): ModelContextMetadata | null {
|
|
325
|
+
const lookupId = providerId === "anthropic"
|
|
326
|
+
? modelId
|
|
327
|
+
: providerId === "openai"
|
|
328
|
+
? normalizeOpenAIModelId(modelId)
|
|
329
|
+
: modelId;
|
|
342
330
|
const entry = KNOWN_CONTEXT_REGISTRY[`${providerId}:${lookupId}`];
|
|
343
331
|
if (!entry) return null;
|
|
344
332
|
return {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { sanitizeTerminalOutput } from "../terminal/terminalSanitize.js";
|
|
2
|
+
import { runAgentLoop, type AgentChatMessage, type AgentChatResponse } from "../agent/loop.js";
|
|
3
|
+
import { parseOpenAiToolCalls } from "../agent/protocol.js";
|
|
2
4
|
import type { BackendRunHandlers } from "../providers/types.js";
|
|
3
5
|
import type { ProviderWorkspaceOverride } from "../providerLauncher/types.js";
|
|
4
6
|
import type {
|
|
@@ -456,21 +458,110 @@ function getCachedSelectedModel(config: LocalProviderConfig, routeModel: string)
|
|
|
456
458
|
return selectFallbackLocalModel(config, discoveredIds) ?? routeModel;
|
|
457
459
|
}
|
|
458
460
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
461
|
+
interface LocalResponseDiagnostics {
|
|
462
|
+
choiceCount: number;
|
|
463
|
+
finishReasons: string[];
|
|
464
|
+
recognizedFields: string[];
|
|
465
|
+
topLevelKeys: string[];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
interface ExtractedLocalResponse extends AgentChatResponse {
|
|
469
|
+
diagnostics: LocalResponseDiagnostics;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function textFromContent(value: unknown): string {
|
|
473
|
+
if (typeof value === "string") return value;
|
|
474
|
+
if (Array.isArray(value)) return value.map(textFromContent).join("");
|
|
475
|
+
if (!isRecord(value)) return "";
|
|
476
|
+
|
|
477
|
+
// OpenAI-compatible servers commonly emit content as typed parts, such as
|
|
478
|
+
// { type: "text", text: "..." } or { type: "output_text", text: "..." }.
|
|
479
|
+
if (typeof value.text === "string") return value.text;
|
|
480
|
+
if (typeof value.content === "string" || Array.isArray(value.content)) {
|
|
481
|
+
return textFromContent(value.content);
|
|
482
|
+
}
|
|
483
|
+
if ((value.type === "text" || value.type === "output_text") && typeof value.value === "string") {
|
|
484
|
+
return value.value;
|
|
485
|
+
}
|
|
486
|
+
return "";
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function messageFieldText(message: Record<string, unknown>, fields: readonly string[], recognizedFields: Set<string>): string {
|
|
490
|
+
for (const field of fields) {
|
|
491
|
+
const text = textFromContent(message[field]);
|
|
492
|
+
if (text.trim()) {
|
|
493
|
+
recognizedFields.add(`message.${field}`);
|
|
494
|
+
return text;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return "";
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function extractNonStreamingResponse(body: unknown): ExtractedLocalResponse {
|
|
501
|
+
const topLevelKeys = isRecord(body) ? Object.keys(body).sort() : [];
|
|
502
|
+
const choices = isRecord(body) && Array.isArray(body.choices) ? body.choices : [];
|
|
503
|
+
const recognizedFields = new Set<string>();
|
|
504
|
+
const finishReasons: string[] = [];
|
|
505
|
+
const toolCalls = choices.flatMap((choice) => {
|
|
506
|
+
if (!isRecord(choice)) return [];
|
|
507
|
+
if (typeof choice.finish_reason === "string") finishReasons.push(choice.finish_reason);
|
|
508
|
+
const message = isRecord(choice.message) ? choice.message : null;
|
|
509
|
+
return message ? parseOpenAiToolCalls(message.tool_calls) : [];
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const content: string[] = [];
|
|
513
|
+
const reasoning: string[] = [];
|
|
514
|
+
for (const choice of choices) {
|
|
515
|
+
if (!isRecord(choice)) continue;
|
|
516
|
+
const message = isRecord(choice.message) ? choice.message : null;
|
|
517
|
+
const messageContent = message
|
|
518
|
+
? messageFieldText(message, ["content"], recognizedFields)
|
|
519
|
+
: "";
|
|
520
|
+
const legacyText = textFromContent(choice.text);
|
|
521
|
+
if (legacyText.trim()) recognizedFields.add("choice.text");
|
|
522
|
+
const answer = messageContent || legacyText;
|
|
523
|
+
if (answer.trim()) {
|
|
524
|
+
content.push(answer);
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const reasoningText = message
|
|
529
|
+
? messageFieldText(message, ["reasoning_content", "reasoning", "analysis"], recognizedFields)
|
|
530
|
+
: "";
|
|
531
|
+
const choiceReasoning = !reasoningText
|
|
532
|
+
? messageFieldText(choice, ["reasoning_content", "reasoning", "analysis"], recognizedFields)
|
|
533
|
+
: "";
|
|
534
|
+
if ((reasoningText || choiceReasoning).trim()) {
|
|
535
|
+
reasoning.push(reasoningText || choiceReasoning);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
// Reasoning is a fallback only. A response with both fields should show its
|
|
541
|
+
// final answer, while reasoning-only servers still produce useful text.
|
|
542
|
+
text: (content.length > 0 ? content : reasoning).join("").trim(),
|
|
543
|
+
...(toolCalls.length > 0 ? { toolCalls } : {}),
|
|
544
|
+
diagnostics: {
|
|
545
|
+
choiceCount: choices.length,
|
|
546
|
+
finishReasons: [...new Set(finishReasons)],
|
|
547
|
+
recognizedFields: [...recognizedFields].sort(),
|
|
548
|
+
topLevelKeys,
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function emptyResponseError(config: LocalProviderConfig, model: string, diagnostics: LocalResponseDiagnostics): Error {
|
|
554
|
+
const finishReasons = diagnostics.finishReasons.length > 0 ? diagnostics.finishReasons.join(", ") : "none";
|
|
555
|
+
const fields = diagnostics.recognizedFields.length > 0 ? diagnostics.recognizedFields.join(", ") : "none";
|
|
556
|
+
const keys = diagnostics.topLevelKeys.length > 0 ? diagnostics.topLevelKeys.join(", ") : "none";
|
|
557
|
+
return new Error([
|
|
558
|
+
"Local OpenAI-compatible API returned no assistant text.",
|
|
559
|
+
`Endpoint: ${config.baseUrl}/chat/completions`,
|
|
560
|
+
`Model: ${model}`,
|
|
561
|
+
`Response details: choices=${diagnostics.choiceCount}; finish_reason=${finishReasons}; recognized_fields=${fields}; top_level_keys=${keys}.`,
|
|
562
|
+
"Check the local server response format or retry the prompt.",
|
|
563
|
+
].join("\n"));
|
|
564
|
+
}
|
|
474
565
|
|
|
475
566
|
function parseStreamDelta(line: string): string | null {
|
|
476
567
|
const trimmed = line.trim();
|
|
@@ -521,18 +612,19 @@ async function postLocalChatCompletion(options: {
|
|
|
521
612
|
request: ProviderChatRequest;
|
|
522
613
|
config: LocalProviderConfig;
|
|
523
614
|
stream: boolean;
|
|
615
|
+
messages?: readonly AgentChatMessage[];
|
|
524
616
|
fetchImpl: FetchImpl;
|
|
525
617
|
signal?: AbortSignal;
|
|
526
618
|
handlers: BackendRunHandlers;
|
|
527
619
|
capProfile: import("./capabilityProfile.js").ModelCapabilityProfile;
|
|
528
|
-
}): Promise<
|
|
620
|
+
}): Promise<AgentChatResponse> {
|
|
529
621
|
const model = getCachedSelectedModel(options.config, options.request.route.modelId);
|
|
530
622
|
const includeSystemPrompt = options.capProfile.supportsSystemPrompt !== false;
|
|
531
|
-
const messages = [
|
|
623
|
+
const messages: readonly AgentChatMessage[] = options.messages ?? [
|
|
532
624
|
...(includeSystemPrompt && options.request.projectInstructions?.content
|
|
533
|
-
? [{ role: "system", content: options.request.projectInstructions.content }]
|
|
625
|
+
? [{ role: "system" as const, content: options.request.projectInstructions.content }]
|
|
534
626
|
: []),
|
|
535
|
-
{ role: "user", content: options.request.prompt },
|
|
627
|
+
{ role: "user" as const, content: options.request.prompt },
|
|
536
628
|
];
|
|
537
629
|
const response = await options.fetchImpl(`${options.config.baseUrl}/chat/completions`, {
|
|
538
630
|
method: "POST",
|
|
@@ -560,11 +652,15 @@ async function postLocalChatCompletion(options: {
|
|
|
560
652
|
}
|
|
561
653
|
|
|
562
654
|
if (options.stream) {
|
|
563
|
-
return readStreamingResponse(response, options.handlers);
|
|
655
|
+
return { text: await readStreamingResponse(response, options.handlers) };
|
|
564
656
|
}
|
|
565
657
|
|
|
566
|
-
const parsed = JSON.parse(await response.text()) as unknown;
|
|
567
|
-
|
|
658
|
+
const parsed = JSON.parse(await response.text()) as unknown;
|
|
659
|
+
const extracted = extractNonStreamingResponse(parsed);
|
|
660
|
+
if (!extracted.text.trim() && (extracted.toolCalls?.length ?? 0) === 0) {
|
|
661
|
+
throw emptyResponseError(options.config, model, extracted.diagnostics);
|
|
662
|
+
}
|
|
663
|
+
return extracted;
|
|
568
664
|
}
|
|
569
665
|
|
|
570
666
|
function isModelNotLoadedError(status: number, body: string): boolean {
|
|
@@ -589,42 +685,24 @@ export async function runLocalOpenAiCompatible(
|
|
|
589
685
|
providerConfig: request.localConfig ?? configuredOverride,
|
|
590
686
|
rawMetadata: rawMeta,
|
|
591
687
|
});
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
688
|
+
const text = await runAgentLoop({
|
|
689
|
+
request,
|
|
690
|
+
handlers,
|
|
691
|
+
includeSystemPrompt: capProfile.supportsSystemPrompt !== false,
|
|
692
|
+
signal: options.signal,
|
|
693
|
+
sendMessages: async (messages) =>
|
|
694
|
+
postLocalChatCompletion({
|
|
597
695
|
request,
|
|
598
696
|
config,
|
|
599
|
-
|
|
697
|
+
messages,
|
|
698
|
+
stream: false,
|
|
600
699
|
fetchImpl,
|
|
601
700
|
signal: options.signal,
|
|
602
701
|
handlers,
|
|
603
702
|
capProfile,
|
|
604
|
-
})
|
|
605
|
-
if (streamed) return streamed;
|
|
606
|
-
} catch (error) {
|
|
607
|
-
if (options.signal?.aborted) throw error;
|
|
608
|
-
handlers.onProgress?.({
|
|
609
|
-
id: "local-stream-fallback",
|
|
610
|
-
source: "stderr",
|
|
611
|
-
text: "Local streaming request failed; retrying without streaming.",
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
const text = await postLocalChatCompletion({
|
|
617
|
-
request,
|
|
618
|
-
config,
|
|
619
|
-
stream: false,
|
|
620
|
-
fetchImpl,
|
|
621
|
-
signal: options.signal,
|
|
622
|
-
handlers,
|
|
623
|
-
capProfile,
|
|
703
|
+
}),
|
|
624
704
|
});
|
|
625
|
-
if (!text)
|
|
626
|
-
throw new Error("Local OpenAI-compatible API returned no assistant text.");
|
|
627
|
-
}
|
|
705
|
+
if (!text) throw new Error("Local OpenAI-compatible API returned no assistant text after tool execution.");
|
|
628
706
|
handlers.onAssistantDelta?.(text);
|
|
629
707
|
return text;
|
|
630
708
|
}
|