@gajae-code/coding-agent 0.3.1 → 0.4.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/CHANGELOG.md +46 -0
- package/README.md +1 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +30 -0
- package/dist/types/config/model-profiles.d.ts +19 -0
- package/dist/types/config/model-registry.d.ts +25 -10
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +84 -0
- package/dist/types/config/settings-schema.d.ts +15 -0
- package/dist/types/edit/diff.d.ts +16 -0
- package/dist/types/edit/modes/replace.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/activation.d.ts +14 -0
- package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/injection.d.ts +31 -0
- package/dist/types/extensibility/gjc-plugins/loader.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/schema.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/state.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/tools.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/validation.d.ts +4 -0
- package/dist/types/extensibility/skills.d.ts +9 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +22 -0
- package/dist/types/harness-control-plane/storage.d.ts +7 -0
- package/dist/types/lsp/client.d.ts +1 -0
- package/dist/types/main.d.ts +10 -1
- package/dist/types/modes/bridge/bridge-mode.d.ts +2 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
- package/dist/types/modes/components/model-selector.d.ts +6 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +9 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +179 -2
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +16 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +47 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +7 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +3 -1
- package/dist/types/modes/shared/agent-wire/responses.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/sdk.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +10 -0
- package/dist/types/session/blob-store.d.ts +17 -0
- package/dist/types/session/messages.d.ts +3 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/skill-state/active-state.d.ts +13 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/thinking.d.ts +3 -2
- package/dist/types/tools/hindsight-recall.d.ts +0 -2
- package/dist/types/tools/hindsight-reflect.d.ts +0 -2
- package/dist/types/tools/hindsight-retain.d.ts +0 -2
- package/dist/types/tools/index.d.ts +7 -4
- package/package.json +9 -7
- package/src/cli/args.ts +10 -0
- package/src/cli.ts +14 -0
- package/src/commands/harness.ts +192 -7
- package/src/commands/launch.ts +8 -0
- package/src/commands/ultragoal.ts +1 -21
- package/src/config/model-equivalence.ts +1 -1
- package/src/config/model-profile-activation.ts +157 -0
- package/src/config/model-profiles.ts +155 -0
- package/src/config/model-registry.ts +51 -5
- package/src/config/model-resolver.ts +3 -2
- package/src/config/models-config-schema.ts +42 -1
- package/src/config/settings-schema.ts +14 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +11 -1
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/discovery/claude-plugins.ts +25 -5
- package/src/edit/diff.ts +64 -1
- package/src/edit/modes/replace.ts +60 -2
- package/src/extensibility/gjc-plugins/activation.ts +87 -0
- package/src/extensibility/gjc-plugins/index.ts +9 -0
- package/src/extensibility/gjc-plugins/injection.ts +114 -0
- package/src/extensibility/gjc-plugins/loader.ts +131 -0
- package/src/extensibility/gjc-plugins/paths.ts +66 -0
- package/src/extensibility/gjc-plugins/schema.ts +79 -0
- package/src/extensibility/gjc-plugins/state.ts +29 -0
- package/src/extensibility/gjc-plugins/tools.ts +47 -0
- package/src/extensibility/gjc-plugins/types.ts +97 -0
- package/src/extensibility/gjc-plugins/validation.ts +76 -0
- package/src/extensibility/skills.ts +39 -7
- package/src/gjc-runtime/state-runtime.ts +93 -2
- package/src/gjc-runtime/state-writer.ts +17 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +62 -2
- package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
- package/src/gjc-runtime/workflow-manifest.ts +2 -2
- package/src/harness-control-plane/storage.ts +144 -2
- package/src/hashline/hash.ts +23 -0
- package/src/hooks/skill-state.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +8 -11
- package/src/lsp/client.ts +7 -0
- package/src/main.ts +67 -1
- package/src/modes/acp/acp-agent.ts +25 -2
- package/src/modes/bridge/bridge-mode.ts +124 -2
- package/src/modes/components/custom-provider-wizard.ts +318 -0
- package/src/modes/components/model-selector.ts +108 -18
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/controllers/input-controller.ts +14 -2
- package/src/modes/controllers/selector-controller.ts +57 -1
- package/src/modes/prompt-action-autocomplete.ts +49 -10
- package/src/modes/rpc/rpc-client.ts +57 -3
- package/src/modes/rpc/rpc-mode.ts +67 -0
- package/src/modes/rpc/rpc-types.ts +224 -2
- package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
- package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
- package/src/modes/shared/agent-wire/command-validation.ts +25 -1
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
- package/src/modes/shared/agent-wire/handshake.ts +43 -3
- package/src/modes/shared/agent-wire/protocol.ts +7 -0
- package/src/modes/shared/agent-wire/responses.ts +2 -2
- package/src/modes/shared/agent-wire/scopes.ts +2 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
- package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/memories/consolidation.md +1 -1
- package/src/prompts/memories/read-path.md +6 -7
- package/src/prompts/memories/unavailable.md +2 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/recall.md +1 -0
- package/src/prompts/tools/reflect.md +1 -0
- package/src/prompts/tools/retain.md +1 -0
- package/src/runtime-mcp/client.ts +7 -4
- package/src/runtime-mcp/manager.ts +45 -13
- package/src/runtime-mcp/transports/http.ts +40 -14
- package/src/runtime-mcp/transports/stdio.ts +11 -10
- package/src/sdk.ts +48 -1
- package/src/session/agent-session.ts +211 -2
- package/src/session/blob-store.ts +84 -0
- package/src/session/messages.ts +3 -0
- package/src/session/session-manager.ts +390 -33
- package/src/session/session-storage.ts +26 -0
- package/src/setup/provider-onboarding.ts +2 -2
- package/src/skill-state/active-state.ts +89 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/task/discovery.ts +7 -1
- package/src/task/executor.ts +18 -2
- package/src/task/index.ts +2 -0
- package/src/thinking.ts +8 -2
- package/src/tools/ask.ts +39 -9
- package/src/tools/hindsight-recall.ts +0 -2
- package/src/tools/hindsight-reflect.ts +0 -2
- package/src/tools/hindsight-retain.ts +0 -2
- package/src/tools/index.ts +7 -18
- package/src/tools/read.ts +3 -3
- package/src/tools/skill.ts +15 -3
- package/src/utils/edit-mode.ts +1 -1
package/src/lsp/client.ts
CHANGED
|
@@ -61,6 +61,10 @@ function stopIdleChecker(): void {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export function isIdleCheckerActiveForTests(): boolean {
|
|
65
|
+
return idleCheckInterval !== null;
|
|
66
|
+
}
|
|
67
|
+
|
|
64
68
|
// =============================================================================
|
|
65
69
|
// Client Capabilities
|
|
66
70
|
// =============================================================================
|
|
@@ -919,6 +923,9 @@ export async function sendNotification(client: LspClient, method: string, params
|
|
|
919
923
|
* Shutdown all LSP clients.
|
|
920
924
|
*/
|
|
921
925
|
export async function shutdownAll(): Promise<void> {
|
|
926
|
+
stopIdleChecker();
|
|
927
|
+
clientLocks.clear();
|
|
928
|
+
fileOperationLocks.clear();
|
|
922
929
|
const clientsToShutdown = Array.from(clients.values());
|
|
923
930
|
clients.clear();
|
|
924
931
|
await Promise.allSettled(clientsToShutdown.map(client => shutdownClientInstance(client)));
|
package/src/main.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { buildInitialMessage } from "./cli/initial-message";
|
|
|
26
26
|
import { runListModelsCommand } from "./cli/list-models";
|
|
27
27
|
import { selectSession } from "./cli/session-picker";
|
|
28
28
|
import { findConfigFile } from "./config";
|
|
29
|
+
import { activateModelProfile } from "./config/model-profile-activation";
|
|
29
30
|
import { ModelRegistry, ModelsConfigFile } from "./config/model-registry";
|
|
30
31
|
import { resolveCliModel, resolveModelRoleValue, resolveModelScope, type ScopedModel } from "./config/model-resolver";
|
|
31
32
|
import { getDefault, type SettingPath, Settings, settings } from "./config/settings";
|
|
@@ -194,11 +195,59 @@ export interface AcpSessionFactoryOptions {
|
|
|
194
195
|
sessionDir?: string;
|
|
195
196
|
authStorage: AuthStorage;
|
|
196
197
|
modelRegistry: ModelRegistry;
|
|
197
|
-
parsedArgs: Pick<Args, "apiKey">;
|
|
198
|
+
parsedArgs: Pick<Args, "apiKey" | "default" | "model" | "mpreset" | "thinking">;
|
|
198
199
|
rawArgs: string[];
|
|
199
200
|
createSession: (options: CreateAgentSessionOptions) => Promise<CreateAgentSessionResult>;
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
export async function applyStartupModelProfiles(args: {
|
|
204
|
+
session: AgentSession;
|
|
205
|
+
settings: Settings;
|
|
206
|
+
modelRegistry: ModelRegistry;
|
|
207
|
+
parsedArgs: Pick<Args, "default" | "model" | "mpreset" | "thinking">;
|
|
208
|
+
startupModel?: CreateAgentSessionOptions["model"];
|
|
209
|
+
startupThinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
|
|
210
|
+
}): Promise<void> {
|
|
211
|
+
const applyProfile = async (profileName: string, persistDefault: boolean): Promise<void> => {
|
|
212
|
+
await activateModelProfile(
|
|
213
|
+
{ session: args.session, modelRegistry: args.modelRegistry, settings: args.settings, profileName },
|
|
214
|
+
{ persistDefault },
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Capture the explicitly-selected startup model BEFORE profile activation can
|
|
219
|
+
// override it. startupModel covers the eager path; session.model covers the
|
|
220
|
+
// deferred `--model <pattern>` path resolved inside createAgentSession.
|
|
221
|
+
const explicitModel = args.parsedArgs.model ? (args.startupModel ?? args.session.model) : undefined;
|
|
222
|
+
|
|
223
|
+
const defaultProfile = args.settings.get("modelProfile.default");
|
|
224
|
+
if (defaultProfile) {
|
|
225
|
+
await applyProfile(defaultProfile, false);
|
|
226
|
+
}
|
|
227
|
+
if (args.parsedArgs.mpreset) {
|
|
228
|
+
await applyProfile(args.parsedArgs.mpreset, args.parsedArgs.default === true);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Explicit CLI --model/--thinking must win over any activated profile.
|
|
232
|
+
if (explicitModel) {
|
|
233
|
+
await args.session.setModelTemporary(explicitModel, args.startupThinkingLevel ?? args.parsedArgs.thinking);
|
|
234
|
+
} else if (args.parsedArgs.thinking && args.session.model) {
|
|
235
|
+
await args.session.setModelTemporary(args.session.model, args.parsedArgs.thinking);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function applyStartupModelProfilesOrExit(
|
|
240
|
+
args: Parameters<typeof applyStartupModelProfiles>[0],
|
|
241
|
+
): Promise<void> {
|
|
242
|
+
try {
|
|
243
|
+
await applyStartupModelProfiles(args);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
246
|
+
process.stderr.write(`${chalk.red(`Error: ${message}`)}\n`);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
202
251
|
/**
|
|
203
252
|
* Build the per-`session/new` factory used by ACP mode.
|
|
204
253
|
*
|
|
@@ -225,6 +274,14 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
|
|
|
225
274
|
hasUI: false,
|
|
226
275
|
enableMCP: false,
|
|
227
276
|
});
|
|
277
|
+
await applyStartupModelProfilesOrExit({
|
|
278
|
+
session: nextSession,
|
|
279
|
+
settings: nextSettings,
|
|
280
|
+
modelRegistry: args.modelRegistry,
|
|
281
|
+
parsedArgs: args.parsedArgs,
|
|
282
|
+
startupModel: args.baseOptions.model,
|
|
283
|
+
startupThinkingLevel: args.baseOptions.thinkingLevel,
|
|
284
|
+
});
|
|
228
285
|
if (args.parsedArgs.apiKey && !args.baseOptions.model && nextSession.model) {
|
|
229
286
|
args.authStorage.setRuntimeApiKey(nextSession.model.provider, args.parsedArgs.apiKey);
|
|
230
287
|
}
|
|
@@ -878,6 +935,15 @@ export async function runRootCommand(
|
|
|
878
935
|
authStorage.setRuntimeApiKey(session.model.provider, parsedArgs.apiKey);
|
|
879
936
|
}
|
|
880
937
|
|
|
938
|
+
await applyStartupModelProfilesOrExit({
|
|
939
|
+
session,
|
|
940
|
+
settings: settingsInstance,
|
|
941
|
+
modelRegistry,
|
|
942
|
+
parsedArgs,
|
|
943
|
+
startupModel: sessionOptions.model,
|
|
944
|
+
startupThinkingLevel: sessionOptions.thinkingLevel,
|
|
945
|
+
});
|
|
946
|
+
|
|
881
947
|
if (modelFallbackMessage) {
|
|
882
948
|
notifs.push({ kind: "warn", message: modelFallbackMessage });
|
|
883
949
|
}
|
|
@@ -50,6 +50,7 @@ import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../
|
|
|
50
50
|
import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../../extensibility/extensions";
|
|
51
51
|
import { runExtensionCompact } from "../../extensibility/extensions/compact-handler";
|
|
52
52
|
import { getSessionSlashCommands } from "../../extensibility/extensions/get-commands-handler";
|
|
53
|
+
import { resolveSubskillActivationForSkillInvocation } from "../../extensibility/gjc-plugins";
|
|
53
54
|
import {
|
|
54
55
|
buildSkillPromptMessage,
|
|
55
56
|
getSkillSlashCommandNames,
|
|
@@ -722,7 +723,18 @@ export class AcpAgent implements Agent {
|
|
|
722
723
|
for (let index = 0; index < invocations.length; index += 1) {
|
|
723
724
|
const invocation = invocations[index];
|
|
724
725
|
if (!invocation) continue;
|
|
725
|
-
const
|
|
726
|
+
const activationResult = await resolveSubskillActivationForSkillInvocation({
|
|
727
|
+
cwd: record.session.sessionManager.getCwd(),
|
|
728
|
+
sessionId: record.session.sessionId,
|
|
729
|
+
skillName: invocation.skill.name,
|
|
730
|
+
args: invocation.args,
|
|
731
|
+
});
|
|
732
|
+
const built = await buildSkillPromptMessage(invocation.skill, activationResult.cleanedArgs, {
|
|
733
|
+
subskillActivation: activationResult.activation,
|
|
734
|
+
subskillActivationSet: activationResult.activeSubskillsToPersist,
|
|
735
|
+
cwd: record.session.sessionManager.getCwd(),
|
|
736
|
+
sessionId: record.session.sessionId,
|
|
737
|
+
});
|
|
726
738
|
if (index === invocations.length - 1) {
|
|
727
739
|
await record.session.promptCustomMessage({
|
|
728
740
|
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
@@ -757,7 +769,18 @@ export class AcpAgent implements Agent {
|
|
|
757
769
|
if (!skill || skill.hide === true) {
|
|
758
770
|
return false;
|
|
759
771
|
}
|
|
760
|
-
const
|
|
772
|
+
const activationResult = await resolveSubskillActivationForSkillInvocation({
|
|
773
|
+
cwd: record.session.sessionManager.getCwd(),
|
|
774
|
+
sessionId: record.session.sessionId,
|
|
775
|
+
skillName: skill.name,
|
|
776
|
+
args,
|
|
777
|
+
});
|
|
778
|
+
const built = await buildSkillPromptMessage(skill, activationResult.cleanedArgs, {
|
|
779
|
+
subskillActivation: activationResult.activation,
|
|
780
|
+
subskillActivationSet: activationResult.activeSubskillsToPersist,
|
|
781
|
+
cwd: record.session.sessionManager.getCwd(),
|
|
782
|
+
sessionId: record.session.sessionId,
|
|
783
|
+
});
|
|
761
784
|
await record.session.promptCustomMessage({
|
|
762
785
|
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
763
786
|
content: built.message,
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
1
2
|
import type { ExtensionUIContext } from "../../extensibility/extensions";
|
|
2
3
|
import type { AgentSession } from "../../session/agent-session";
|
|
3
4
|
import type { ClientBridgePermissionOutcome } from "../../session/client-bridge";
|
|
4
|
-
import type { RpcCommand, RpcResponse } from "../rpc/rpc-types";
|
|
5
|
+
import type { RpcCommand, RpcResponse, RpcWorkflowGateResponse } from "../rpc/rpc-types";
|
|
5
6
|
import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
|
|
6
7
|
import { isRpcCommand } from "../shared/agent-wire/command-validation";
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
BridgeFrameSequencer,
|
|
10
|
+
toBridgeEventFrame,
|
|
11
|
+
toBridgeWorkflowGateFrame,
|
|
12
|
+
} from "../shared/agent-wire/event-envelope";
|
|
8
13
|
import type { BridgeCapability } from "../shared/agent-wire/handshake";
|
|
9
14
|
import {
|
|
10
15
|
type BridgeHandshakeRequest,
|
|
@@ -23,6 +28,9 @@ import {
|
|
|
23
28
|
} from "../shared/agent-wire/scopes";
|
|
24
29
|
import { UiRequestBroker } from "../shared/agent-wire/ui-request-broker";
|
|
25
30
|
import type { BridgeUiResult } from "../shared/agent-wire/ui-result";
|
|
31
|
+
import { defaultAuditPath, UnattendedAuditLog } from "../shared/agent-wire/unattended-audit";
|
|
32
|
+
import { UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
|
|
33
|
+
import { FileGateStore } from "../shared/agent-wire/workflow-gate-broker";
|
|
26
34
|
import { assertSafeBridgeBind, isBridgeTokenAuthorized } from "./auth";
|
|
27
35
|
import { type BridgePermissionRequestPayload, createBridgeClientBridge } from "./bridge-client-bridge";
|
|
28
36
|
import { BridgeExtensionUIContext, type BridgeUiRequestPayload } from "./bridge-ui-context";
|
|
@@ -39,6 +47,7 @@ const SERVER_CAPABILITIES: readonly BridgeCapability[] = [
|
|
|
39
47
|
"ui.declarative",
|
|
40
48
|
"host_tools",
|
|
41
49
|
"host_uri",
|
|
50
|
+
"workflow_gate",
|
|
42
51
|
];
|
|
43
52
|
|
|
44
53
|
const DEFAULT_BRIDGE_SCOPES: readonly BridgeCommandScope[] = ["prompt"];
|
|
@@ -71,6 +80,7 @@ const SERVER_FRAME_TYPES: readonly BridgeFrameType[] = [
|
|
|
71
80
|
"host_tool_call",
|
|
72
81
|
"host_uri_request",
|
|
73
82
|
"reset",
|
|
83
|
+
"workflow_gate",
|
|
74
84
|
"error",
|
|
75
85
|
];
|
|
76
86
|
|
|
@@ -86,6 +96,7 @@ interface BridgeFetchHandlerOptions {
|
|
|
86
96
|
hostToolBridge?: RpcHostToolBridge;
|
|
87
97
|
hostUriBridge?: RpcHostUriBridge;
|
|
88
98
|
endpointMatrix?: Partial<BridgeEndpointMatrix>;
|
|
99
|
+
unattendedControlPlane?: UnattendedSessionControlPlane;
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
interface BridgeIdempotencyRecord {
|
|
@@ -174,6 +185,15 @@ function bridgeHelpResponse(matrix: BridgeEndpointMatrix): Response {
|
|
|
174
185
|
});
|
|
175
186
|
}
|
|
176
187
|
|
|
188
|
+
function auditOutcomeFor(event: string): "accepted" | "rejected" | "denied" | "exceeded" | "aborted" | "info" {
|
|
189
|
+
if (event.includes("denied")) return "denied";
|
|
190
|
+
if (event.includes("exceeded")) return "exceeded";
|
|
191
|
+
if (event.includes("abort")) return "aborted";
|
|
192
|
+
if (event.includes("rejected") || event.includes("conflict")) return "rejected";
|
|
193
|
+
if (event.includes("accepted") || event.includes("negotiated") || event.includes("emitted")) return "accepted";
|
|
194
|
+
return "info";
|
|
195
|
+
}
|
|
196
|
+
|
|
177
197
|
function frameTypeForDispatchOutput(obj: RpcResponse | object): BridgeFrameType {
|
|
178
198
|
const type = typeof obj === "object" && obj !== null && "type" in obj ? (obj as { type?: unknown }).type : undefined;
|
|
179
199
|
if (type === "host_tool_call" || type === "host_tool_cancel") return "host_tool_call";
|
|
@@ -219,6 +239,24 @@ export function createBridgeFetchHandler(options: BridgeFetchHandlerOptions): (r
|
|
|
219
239
|
if (!isBridgeHandshakeRequest(payload)) {
|
|
220
240
|
return jsonResponse(400, { error: "invalid_request" });
|
|
221
241
|
}
|
|
242
|
+
let acceptedUnattended = options.unattendedControlPlane?.isUnattended() ? payload.unattended : undefined;
|
|
243
|
+
if (
|
|
244
|
+
acceptedUnattended === undefined &&
|
|
245
|
+
payload.unattended !== undefined &&
|
|
246
|
+
endpointMatrix.events &&
|
|
247
|
+
options.unattendedControlPlane
|
|
248
|
+
) {
|
|
249
|
+
try {
|
|
250
|
+
options.unattendedControlPlane.negotiate(payload.unattended);
|
|
251
|
+
acceptedUnattended = payload.unattended;
|
|
252
|
+
} catch (err) {
|
|
253
|
+
const error =
|
|
254
|
+
err instanceof Error && "code" in err
|
|
255
|
+
? { code: (err as { code: unknown }).code, message: err.message }
|
|
256
|
+
: { error: err instanceof Error ? err.message : String(err) };
|
|
257
|
+
return jsonResponse(403, error);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
222
260
|
return jsonResponse(
|
|
223
261
|
200,
|
|
224
262
|
negotiateBridgeHandshake(payload, {
|
|
@@ -243,6 +281,7 @@ export function createBridgeFetchHandler(options: BridgeFetchHandlerOptions): (r
|
|
|
243
281
|
: "",
|
|
244
282
|
},
|
|
245
283
|
frameTypes: endpointMatrix.events ? SERVER_FRAME_TYPES : [],
|
|
284
|
+
acceptedUnattended,
|
|
246
285
|
}),
|
|
247
286
|
);
|
|
248
287
|
}
|
|
@@ -360,6 +399,36 @@ export function createBridgeFetchHandler(options: BridgeFetchHandlerOptions): (r
|
|
|
360
399
|
} catch {
|
|
361
400
|
return jsonResponse(400, { error: "invalid_json" });
|
|
362
401
|
}
|
|
402
|
+
if (
|
|
403
|
+
payload !== null &&
|
|
404
|
+
typeof payload === "object" &&
|
|
405
|
+
"gate_id" in payload &&
|
|
406
|
+
"answer" in payload &&
|
|
407
|
+
(correlationId === (payload as RpcWorkflowGateResponse).gate_id || correlationId.startsWith("wg_"))
|
|
408
|
+
) {
|
|
409
|
+
try {
|
|
410
|
+
const resolution = await options.unattendedControlPlane?.resolveGate({
|
|
411
|
+
gate_id: (payload as RpcWorkflowGateResponse).gate_id,
|
|
412
|
+
answer: (payload as RpcWorkflowGateResponse).answer,
|
|
413
|
+
idempotency_key: (payload as RpcWorkflowGateResponse).idempotency_key ?? idempotencyKey,
|
|
414
|
+
});
|
|
415
|
+
if (resolution) {
|
|
416
|
+
rememberIdempotencyResponse(options.idempotencyCache, idempotencyKey, {
|
|
417
|
+
route: url.pathname,
|
|
418
|
+
ownerToken,
|
|
419
|
+
body,
|
|
420
|
+
response: resolution,
|
|
421
|
+
});
|
|
422
|
+
return jsonResponse(200, resolution);
|
|
423
|
+
}
|
|
424
|
+
} catch (err) {
|
|
425
|
+
const error =
|
|
426
|
+
err instanceof Error && "code" in err
|
|
427
|
+
? { code: (err as { code: unknown }).code, message: err.message }
|
|
428
|
+
: { error: err instanceof Error ? err.message : String(err) };
|
|
429
|
+
return jsonResponse(409, error);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
363
432
|
const permissionResult = options.permissionBroker?.respond(
|
|
364
433
|
correlationId,
|
|
365
434
|
ownerToken,
|
|
@@ -490,6 +559,57 @@ export async function runBridgeMode(
|
|
|
490
559
|
const hostToolBridge = new RpcHostToolBridge(output);
|
|
491
560
|
const hostUriBridge = new RpcHostUriBridge(output);
|
|
492
561
|
const idempotencyCache: BridgeIdempotencyCache = new Map();
|
|
562
|
+
const auditLog = new UnattendedAuditLog(defaultAuditPath(session.sessionId, session.sessionManager.getCwd()), {
|
|
563
|
+
redactAnswers: true,
|
|
564
|
+
});
|
|
565
|
+
const recordAudit = (event: { event: string; [key: string]: unknown }) => {
|
|
566
|
+
const payload =
|
|
567
|
+
typeof event.payload === "object" && event.payload !== null
|
|
568
|
+
? (event.payload as Record<string, unknown>)
|
|
569
|
+
: undefined;
|
|
570
|
+
const gateId =
|
|
571
|
+
typeof event.gate_id === "string"
|
|
572
|
+
? event.gate_id
|
|
573
|
+
: typeof payload?.gate_id === "string"
|
|
574
|
+
? payload.gate_id
|
|
575
|
+
: undefined;
|
|
576
|
+
auditLog.record({
|
|
577
|
+
run_id: session.sessionId,
|
|
578
|
+
session_id: session.sessionId,
|
|
579
|
+
actor: typeof event.actor === "string" ? event.actor : undefined,
|
|
580
|
+
event: event.event,
|
|
581
|
+
outcome: auditOutcomeFor(event.event),
|
|
582
|
+
dedupe_key: `${event.event}:${gateId ?? "run"}:${JSON.stringify(payload ?? event)}`,
|
|
583
|
+
gate_id: gateId,
|
|
584
|
+
stage: typeof event.stage === "string" ? (event.stage as never) : undefined,
|
|
585
|
+
kind: typeof event.kind === "string" ? (event.kind as never) : undefined,
|
|
586
|
+
scope: typeof payload?.scope === "string" ? payload.scope : undefined,
|
|
587
|
+
action: typeof payload?.action === "string" ? payload.action : undefined,
|
|
588
|
+
budget: event.event === "budget_exceeded" ? (payload as never) : undefined,
|
|
589
|
+
answer_hash: typeof event.answer_hash === "string" ? event.answer_hash : undefined,
|
|
590
|
+
error: payload && event.event.endsWith("denied") ? payload : undefined,
|
|
591
|
+
});
|
|
592
|
+
};
|
|
593
|
+
const gateStore = new FileGateStore(
|
|
594
|
+
path.join(session.sessionManager.getCwd(), ".gjc", "state", "workflow-gates", `${session.sessionId}.json`),
|
|
595
|
+
);
|
|
596
|
+
const unattendedControlPlane = new UnattendedSessionControlPlane({
|
|
597
|
+
runId: session.sessionId,
|
|
598
|
+
sessionId: session.sessionId,
|
|
599
|
+
emitFrame: gate => eventStream.publish(toBridgeWorkflowGateFrame(gate, sequencer)),
|
|
600
|
+
store: gateStore,
|
|
601
|
+
audit: recordAudit,
|
|
602
|
+
getUsageSnapshot: () => {
|
|
603
|
+
const stats = session.getSessionStats();
|
|
604
|
+
return { tokens: stats.tokens.total, costUsd: stats.cost };
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
session.setWorkflowGateEmitter(unattendedControlPlane);
|
|
608
|
+
unattendedControlPlane
|
|
609
|
+
.recover()
|
|
610
|
+
.catch(err =>
|
|
611
|
+
eventStream.publish(sequencer.next("error", { error: err instanceof Error ? err.message : String(err) })),
|
|
612
|
+
);
|
|
493
613
|
|
|
494
614
|
Bun.serve({
|
|
495
615
|
hostname,
|
|
@@ -505,6 +625,7 @@ export async function runBridgeMode(
|
|
|
505
625
|
hostToolBridge,
|
|
506
626
|
hostUriBridge,
|
|
507
627
|
commandScopes,
|
|
628
|
+
unattendedControlPlane,
|
|
508
629
|
commandDispatcher: command =>
|
|
509
630
|
dispatchRpcCommand(command, {
|
|
510
631
|
session,
|
|
@@ -512,6 +633,7 @@ export async function runBridgeMode(
|
|
|
512
633
|
hostToolRegistry: hostToolBridge,
|
|
513
634
|
hostUriRegistry: hostUriBridge,
|
|
514
635
|
createUiContext: () => uiContext,
|
|
636
|
+
unattendedControlPlane,
|
|
515
637
|
}),
|
|
516
638
|
}),
|
|
517
639
|
});
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { Container, Input, matchesKey, Spacer, Text, TruncatedText } from "@gajae-code/tui";
|
|
2
|
+
import type { ProviderCompatibility, ProviderSetupInput } from "../../setup/provider-onboarding";
|
|
3
|
+
import { theme } from "../theme/theme";
|
|
4
|
+
import { matchesAppInterrupt } from "../utils/keybinding-matchers";
|
|
5
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
6
|
+
|
|
7
|
+
export type CustomProviderCredentialSource = "env" | "literal";
|
|
8
|
+
|
|
9
|
+
type WizardStep =
|
|
10
|
+
| "compatibility"
|
|
11
|
+
| "provider-id"
|
|
12
|
+
| "base-url"
|
|
13
|
+
| "credential-source"
|
|
14
|
+
| "credential"
|
|
15
|
+
| "models"
|
|
16
|
+
| "confirm"
|
|
17
|
+
| "force-confirm";
|
|
18
|
+
|
|
19
|
+
interface WizardState {
|
|
20
|
+
compatibility: ProviderCompatibility;
|
|
21
|
+
providerId: string;
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
credentialSource: CustomProviderCredentialSource;
|
|
24
|
+
credential: string;
|
|
25
|
+
models: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type CustomProviderWizardSubmit = ProviderSetupInput;
|
|
29
|
+
|
|
30
|
+
export class CustomProviderWizardComponent extends Container {
|
|
31
|
+
#contentContainer: Container;
|
|
32
|
+
#input: Input | null = null;
|
|
33
|
+
#step: WizardStep = "compatibility";
|
|
34
|
+
#selectedIndex = 0;
|
|
35
|
+
#lastSubmitError: string | null = null;
|
|
36
|
+
#state: WizardState = {
|
|
37
|
+
compatibility: "openai",
|
|
38
|
+
providerId: "",
|
|
39
|
+
baseUrl: "",
|
|
40
|
+
credentialSource: "env",
|
|
41
|
+
credential: "",
|
|
42
|
+
models: "",
|
|
43
|
+
};
|
|
44
|
+
#onSubmit: (input: CustomProviderWizardSubmit) => void;
|
|
45
|
+
#onCancel: () => void;
|
|
46
|
+
#onRender: () => void;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
onSubmit: (input: CustomProviderWizardSubmit) => void,
|
|
50
|
+
onCancel: () => void,
|
|
51
|
+
onRender: () => void = () => {},
|
|
52
|
+
) {
|
|
53
|
+
super();
|
|
54
|
+
this.#onSubmit = onSubmit;
|
|
55
|
+
this.#onCancel = onCancel;
|
|
56
|
+
this.#onRender = onRender;
|
|
57
|
+
|
|
58
|
+
this.addChild(new DynamicBorder());
|
|
59
|
+
this.addChild(new Spacer(1));
|
|
60
|
+
this.addChild(new TruncatedText(theme.bold("Add custom provider")));
|
|
61
|
+
this.addChild(
|
|
62
|
+
new TruncatedText(theme.fg("muted", " Configure an OpenAI- or Anthropic-compatible API provider."), 0, 0),
|
|
63
|
+
);
|
|
64
|
+
this.addChild(new Spacer(1));
|
|
65
|
+
this.#contentContainer = new Container();
|
|
66
|
+
this.addChild(this.#contentContainer);
|
|
67
|
+
this.addChild(new Spacer(1));
|
|
68
|
+
this.addChild(new DynamicBorder());
|
|
69
|
+
this.#renderStep();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setSubmitError(error: string): void {
|
|
73
|
+
this.#lastSubmitError = error;
|
|
74
|
+
if (error.includes("already exists")) {
|
|
75
|
+
this.#step = "force-confirm";
|
|
76
|
+
this.#selectedIndex = 1;
|
|
77
|
+
}
|
|
78
|
+
this.#renderStep();
|
|
79
|
+
this.#onRender();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
handleInput(keyData: string): void {
|
|
83
|
+
if (matchesAppInterrupt(keyData)) {
|
|
84
|
+
if (this.#step === "compatibility") {
|
|
85
|
+
this.#onCancel();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.#goBack();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (this.#input) {
|
|
93
|
+
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
94
|
+
this.#saveInputAndProceed();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.#input.handleInput(keyData);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (matchesKey(keyData, "up")) {
|
|
102
|
+
this.#moveSelection(-1);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (matchesKey(keyData, "down")) {
|
|
106
|
+
this.#moveSelection(1);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
110
|
+
this.#selectCurrentOption();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#renderStep(): void {
|
|
115
|
+
this.#contentContainer.clear();
|
|
116
|
+
this.#input = null;
|
|
117
|
+
switch (this.#step) {
|
|
118
|
+
case "compatibility":
|
|
119
|
+
this.#renderCompatibilityStep();
|
|
120
|
+
break;
|
|
121
|
+
case "provider-id":
|
|
122
|
+
this.#renderInputStep(
|
|
123
|
+
"Step 2: Provider id",
|
|
124
|
+
"Enter a provider id:",
|
|
125
|
+
this.#state.providerId,
|
|
126
|
+
"e.g. my-openai-proxy",
|
|
127
|
+
);
|
|
128
|
+
break;
|
|
129
|
+
case "base-url":
|
|
130
|
+
this.#renderInputStep(
|
|
131
|
+
"Step 3: Base URL",
|
|
132
|
+
"Enter the API base URL:",
|
|
133
|
+
this.#state.baseUrl,
|
|
134
|
+
"e.g. https://api.example.com/v1",
|
|
135
|
+
);
|
|
136
|
+
break;
|
|
137
|
+
case "credential-source":
|
|
138
|
+
this.#renderCredentialSourceStep();
|
|
139
|
+
break;
|
|
140
|
+
case "credential":
|
|
141
|
+
this.#renderInputStep(
|
|
142
|
+
"Step 5: Credential",
|
|
143
|
+
this.#state.credentialSource === "env"
|
|
144
|
+
? "Enter the API key environment variable name:"
|
|
145
|
+
: "Paste the API key:",
|
|
146
|
+
this.#state.credential,
|
|
147
|
+
this.#state.credentialSource === "env"
|
|
148
|
+
? "e.g. OPENAI_API_KEY"
|
|
149
|
+
: "The key will be stored in models.yml and redacted in output.",
|
|
150
|
+
);
|
|
151
|
+
break;
|
|
152
|
+
case "models":
|
|
153
|
+
this.#renderInputStep(
|
|
154
|
+
"Step 6: Model id(s)",
|
|
155
|
+
"Enter model ids, comma-separated:",
|
|
156
|
+
this.#state.models,
|
|
157
|
+
"e.g. gpt-5, claude-sonnet-4-5",
|
|
158
|
+
);
|
|
159
|
+
break;
|
|
160
|
+
case "confirm":
|
|
161
|
+
this.#renderConfirmStep(false);
|
|
162
|
+
break;
|
|
163
|
+
case "force-confirm":
|
|
164
|
+
this.#renderConfirmStep(true);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#renderCompatibilityStep(): void {
|
|
170
|
+
this.#contentContainer.addChild(new Text(theme.fg("accent", "Step 1: Compatibility")));
|
|
171
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
172
|
+
const options: Array<{ value: ProviderCompatibility; label: string }> = [
|
|
173
|
+
{ value: "openai", label: "OpenAI-compatible" },
|
|
174
|
+
{ value: "anthropic", label: "Anthropic-compatible" },
|
|
175
|
+
];
|
|
176
|
+
for (let i = 0; i < options.length; i++) this.#addOption(i, options[i]?.label ?? "");
|
|
177
|
+
this.#addHelp("[↑↓ to navigate, Enter to select, Esc to cancel]");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#renderCredentialSourceStep(): void {
|
|
181
|
+
this.#contentContainer.addChild(new Text(theme.fg("accent", "Step 4: Credential source")));
|
|
182
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
183
|
+
this.#addOption(0, "Environment variable");
|
|
184
|
+
this.#addOption(1, "Paste API key");
|
|
185
|
+
this.#addHelp("[↑↓ to navigate, Enter to select, Esc to go back]");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#renderInputStep(title: string, prompt: string, value: string, hint: string): void {
|
|
189
|
+
this.#contentContainer.addChild(new Text(theme.fg("accent", title)));
|
|
190
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
191
|
+
this.#contentContainer.addChild(new Text(prompt, 0, 0));
|
|
192
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
193
|
+
this.#input = new Input();
|
|
194
|
+
this.#input.setValue(value);
|
|
195
|
+
this.#contentContainer.addChild(this.#input);
|
|
196
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
197
|
+
this.#addHelp(hint);
|
|
198
|
+
this.#addHelp("[Enter to continue, Esc to go back]");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#renderConfirmStep(force: boolean): void {
|
|
202
|
+
this.#contentContainer.addChild(
|
|
203
|
+
new Text(theme.fg("accent", force ? "Provider exists — replace it?" : "Confirm custom provider")),
|
|
204
|
+
);
|
|
205
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
206
|
+
if (this.#lastSubmitError) {
|
|
207
|
+
this.#contentContainer.addChild(new Text(theme.fg(force ? "warning" : "error", this.#lastSubmitError), 0, 0));
|
|
208
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
209
|
+
}
|
|
210
|
+
this.#contentContainer.addChild(new Text(`Compatibility: ${this.#state.compatibility}`, 0, 0));
|
|
211
|
+
this.#contentContainer.addChild(new Text(`Provider: ${this.#state.providerId}`, 0, 0));
|
|
212
|
+
this.#contentContainer.addChild(new Text(`Base URL: ${this.#state.baseUrl}`, 0, 0));
|
|
213
|
+
this.#contentContainer.addChild(
|
|
214
|
+
new Text(
|
|
215
|
+
`Credential: ${this.#state.credentialSource === "env" ? this.#state.credential : "pasted API key"}`,
|
|
216
|
+
0,
|
|
217
|
+
0,
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
this.#contentContainer.addChild(new Text(`Models: ${this.#state.models}`, 0, 0));
|
|
221
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
222
|
+
this.#addOption(0, force ? "Replace existing provider" : "Add provider");
|
|
223
|
+
this.#addOption(1, "Go back");
|
|
224
|
+
this.#addHelp("[↑↓ to navigate, Enter to select, Esc to go back]");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#addOption(index: number, label: string): void {
|
|
228
|
+
const selected = index === this.#selectedIndex;
|
|
229
|
+
const prefix = selected ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
230
|
+
this.#contentContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#addHelp(text: string): void {
|
|
234
|
+
this.#contentContainer.addChild(new Text(theme.fg("muted", text), 0, 0));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#saveInputAndProceed(): void {
|
|
238
|
+
const value = this.#input?.getValue().trim() ?? "";
|
|
239
|
+
if (!value) return;
|
|
240
|
+
if (this.#step === "provider-id") {
|
|
241
|
+
this.#state.providerId = value;
|
|
242
|
+
this.#step = "base-url";
|
|
243
|
+
} else if (this.#step === "base-url") {
|
|
244
|
+
this.#state.baseUrl = value;
|
|
245
|
+
this.#step = "credential-source";
|
|
246
|
+
this.#selectedIndex = 0;
|
|
247
|
+
} else if (this.#step === "credential") {
|
|
248
|
+
this.#state.credential = value;
|
|
249
|
+
this.#step = "models";
|
|
250
|
+
} else if (this.#step === "models") {
|
|
251
|
+
this.#state.models = value;
|
|
252
|
+
this.#step = "confirm";
|
|
253
|
+
this.#selectedIndex = 0;
|
|
254
|
+
this.#lastSubmitError = null;
|
|
255
|
+
}
|
|
256
|
+
this.#renderStep();
|
|
257
|
+
this.#onRender();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#selectCurrentOption(): void {
|
|
261
|
+
if (this.#step === "compatibility") {
|
|
262
|
+
this.#state.compatibility = this.#selectedIndex === 0 ? "openai" : "anthropic";
|
|
263
|
+
this.#step = "provider-id";
|
|
264
|
+
} else if (this.#step === "credential-source") {
|
|
265
|
+
this.#state.credentialSource = this.#selectedIndex === 0 ? "env" : "literal";
|
|
266
|
+
this.#state.credential = "";
|
|
267
|
+
this.#step = "credential";
|
|
268
|
+
} else if (this.#step === "confirm" || this.#step === "force-confirm") {
|
|
269
|
+
if (this.#selectedIndex === 0) {
|
|
270
|
+
this.#onSubmit(this.#buildInput(this.#step === "force-confirm"));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
this.#step = "models";
|
|
274
|
+
}
|
|
275
|
+
this.#renderStep();
|
|
276
|
+
this.#onRender();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#buildInput(force: boolean): CustomProviderWizardSubmit {
|
|
280
|
+
return {
|
|
281
|
+
compatibility: this.#state.compatibility,
|
|
282
|
+
providerId: this.#state.providerId,
|
|
283
|
+
baseUrl: this.#state.baseUrl,
|
|
284
|
+
apiKeyEnv: this.#state.credentialSource === "env" ? this.#state.credential : undefined,
|
|
285
|
+
apiKey: this.#state.credentialSource === "literal" ? this.#state.credential : undefined,
|
|
286
|
+
models: this.#state.models
|
|
287
|
+
.split(",")
|
|
288
|
+
.map(model => model.trim())
|
|
289
|
+
.filter(Boolean),
|
|
290
|
+
force,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#moveSelection(delta: number): void {
|
|
295
|
+
const maxIndex =
|
|
296
|
+
this.#step === "confirm" ||
|
|
297
|
+
this.#step === "force-confirm" ||
|
|
298
|
+
this.#step === "compatibility" ||
|
|
299
|
+
this.#step === "credential-source"
|
|
300
|
+
? 1
|
|
301
|
+
: 0;
|
|
302
|
+
this.#selectedIndex = (this.#selectedIndex + delta + maxIndex + 1) % (maxIndex + 1);
|
|
303
|
+
this.#renderStep();
|
|
304
|
+
this.#onRender();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#goBack(): void {
|
|
308
|
+
if (this.#step === "provider-id") this.#step = "compatibility";
|
|
309
|
+
else if (this.#step === "base-url") this.#step = "provider-id";
|
|
310
|
+
else if (this.#step === "credential-source") this.#step = "base-url";
|
|
311
|
+
else if (this.#step === "credential") this.#step = "credential-source";
|
|
312
|
+
else if (this.#step === "models") this.#step = "credential";
|
|
313
|
+
else if (this.#step === "confirm" || this.#step === "force-confirm") this.#step = "models";
|
|
314
|
+
this.#selectedIndex = 0;
|
|
315
|
+
this.#renderStep();
|
|
316
|
+
this.#onRender();
|
|
317
|
+
}
|
|
318
|
+
}
|