@gajae-code/coding-agent 0.15.5 → 0.16.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 +85 -1
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
- package/dist/types/cli/model-presets-cli.d.ts +9 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +15 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +120 -0
- package/dist/types/config/file-lock.d.ts +54 -2
- package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
- package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +3 -2
- package/dist/types/config/model-preset-registry.d.ts +368 -0
- package/dist/types/config/model-profile-activation.d.ts +39 -0
- package/dist/types/config/model-profiles.d.ts +5 -3
- package/dist/types/config/model-registry.d.ts +17 -5
- package/dist/types/config/settings-schema.d.ts +19 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +2 -5
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/jobs-observer.d.ts +36 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +80 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/state-version.d.ts +4 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/index.d.ts +6 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/model-pin.d.ts +7 -2
- package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +67 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +2 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
- package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +24 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
- package/dist/types/session/sdk-run-capability.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/telemetry/control.d.ts +11 -0
- package/dist/types/telemetry/events.d.ts +20 -0
- package/dist/types/telemetry/index.d.ts +3 -0
- package/dist/types/telemetry/transport.d.ts +22 -0
- package/dist/types/tools/bash-interactive.d.ts +23 -0
- package/dist/types/tools/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -5
- package/dist/types/web/search/providers/base.d.ts +9 -4
- package/dist/types/web/search/providers/xai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +14 -0
- package/package.json +10 -7
- package/scripts/generate-hotkeys-docs.ts +4 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -3
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +671 -39
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/auth-broker-cli.ts +40 -12
- package/src/cli/auth-gateway-cli.ts +273 -97
- package/src/cli/config-cli.ts +2 -2
- package/src/cli/fast-help.ts +4 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +117 -16
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/launch.ts +86 -18
- package/src/commands/model-presets.ts +24 -0
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +44 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +890 -99
- package/src/config/internal/model-preset-registry-test-state.ts +28 -0
- package/src/config/internal/model-preset-registry-test-support.ts +11 -0
- package/src/config/keybindings.ts +6 -2
- package/src/config/model-preset-registry.ts +2795 -0
- package/src/config/model-profile-activation.ts +115 -28
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +56 -14
- package/src/config/model-registry.ts +1044 -122
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +19 -1
- package/src/config/settings.ts +11 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/projection-scan.ts +592 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +111 -42
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/extensibility/extensions/loader.ts +0 -1
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
- package/src/gjc-runtime/empty-delete-gc.ts +301 -0
- package/src/gjc-runtime/gc-render.ts +29 -0
- package/src/gjc-runtime/gc-runtime.ts +174 -11
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +1321 -147
- package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +66 -4
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +616 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +29 -10
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +51 -6
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/fold-receipt.md +3 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +17 -2
- package/src/sdk/broker/broker.ts +1671 -34
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +217 -46
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +548 -0
- package/src/sdk/broker/state-version.ts +26 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
- package/src/sdk/bus/index.ts +51 -4
- package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
- package/src/sdk/bus/prompt-reconciliation.ts +28 -4
- package/src/sdk/bus/slack-conversation.ts +5 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +11 -8
- package/src/sdk/cli/session-cli.ts +276 -6
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +101 -37
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/host.ts +27 -2
- package/src/sdk/host/model-pin.ts +185 -25
- package/src/sdk/host/sdk-run-capability.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +581 -43
- package/src/sdk/host/session-runtime.ts +655 -101
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- package/src/sdk/prompt-deadline-manager.ts +16 -1
- package/src/sdk/protocol/adapter-validation.ts +27 -0
- package/src/sdk/protocol/operation-inventory.generated.json +53 -2
- package/src/sdk/protocol/operation-registry.ts +17 -4
- package/src/sdk/router/session-router.ts +294 -154
- package/src/sdk/session.ts +440 -167
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +1292 -180
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/sdk-run-capability-internal.ts +15 -0
- package/src/session/sdk-run-capability.ts +2 -0
- package/src/session/session-manager.ts +342 -28
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/setup/provider-presets.json +1 -1
- package/src/slash-commands/helpers/credential-switch.ts +6 -1
- package/src/task/executor.ts +19 -3
- package/src/telemetry/control.ts +26 -0
- package/src/telemetry/events.ts +1268 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/transport.ts +87 -0
- package/src/tools/bash-interactive.ts +182 -85
- package/src/tools/bash.ts +1028 -109
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +11 -5
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/web/search/provider.ts +120 -11
- package/src/web/search/providers/anthropic.ts +89 -17
- package/src/web/search/providers/base.ts +9 -4
- package/src/web/search/providers/gemini.ts +87 -15
- package/src/web/search/providers/openai-compatible.ts +23 -8
- package/src/web/search/providers/xai.ts +98 -12
- package/src/web/search/types.ts +16 -0
package/src/tools/gh.ts
CHANGED
|
@@ -2806,6 +2806,14 @@ export interface PrDiffFile {
|
|
|
2806
2806
|
changeType: "modified" | "added" | "deleted" | "renamed" | "binary";
|
|
2807
2807
|
/** Pre-image path for renames/deletes; same as `path` otherwise. */
|
|
2808
2808
|
oldPath?: string;
|
|
2809
|
+
/**
|
|
2810
|
+
* Set when `path` kept its C-style escaped source form because the quoted
|
|
2811
|
+
* token held invalid UTF-8 or a malformed escape; distinguishes it from a
|
|
2812
|
+
* valid path whose decoded text looks identical.
|
|
2813
|
+
*/
|
|
2814
|
+
pathEscaped?: boolean;
|
|
2815
|
+
/** Same as {@link PrDiffFile.pathEscaped}, for `oldPath`. */
|
|
2816
|
+
oldPathEscaped?: boolean;
|
|
2809
2817
|
/** Byte offset of the section's `diff --git` line in the unified diff. */
|
|
2810
2818
|
startOffset: number;
|
|
2811
2819
|
/** Byte offset of the next section (or end-of-text). */
|
|
@@ -2861,6 +2869,8 @@ export function parsePrUnifiedDiff(text: string): PrDiffPayload {
|
|
|
2861
2869
|
interface ParsedDiffHeaderToken {
|
|
2862
2870
|
value: string;
|
|
2863
2871
|
nextIndex: number;
|
|
2872
|
+
/** Set when the token kept its C-style escaped source form. */
|
|
2873
|
+
escaped?: boolean;
|
|
2864
2874
|
}
|
|
2865
2875
|
|
|
2866
2876
|
function skipDiffHeaderSpaces(text: string, index: number): number {
|
|
@@ -2869,63 +2879,87 @@ function skipDiffHeaderSpaces(text: string, index: number): number {
|
|
|
2869
2879
|
return i;
|
|
2870
2880
|
}
|
|
2871
2881
|
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2882
|
+
interface ParsedDiffQuotedEscape {
|
|
2883
|
+
byte: number;
|
|
2884
|
+
nextIndex: number;
|
|
2885
|
+
}
|
|
2875
2886
|
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
while (end < text.length && end < slashIndex + 4) {
|
|
2879
|
-
const digit = text.charAt(end);
|
|
2880
|
-
if (digit < "0" || digit > "7") break;
|
|
2881
|
-
end += 1;
|
|
2882
|
-
}
|
|
2883
|
-
return {
|
|
2884
|
-
value: String.fromCharCode(Number.parseInt(text.slice(slashIndex + 1, end), 8)),
|
|
2885
|
-
nextIndex: end,
|
|
2886
|
-
};
|
|
2887
|
-
}
|
|
2887
|
+
const DIFF_PATH_ENCODER = new TextEncoder();
|
|
2888
|
+
const DIFF_PATH_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
2888
2889
|
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2890
|
+
const DIFF_NAMED_ESCAPE_BYTES: Record<string, number> = {
|
|
2891
|
+
a: 0x07,
|
|
2892
|
+
b: 0x08,
|
|
2893
|
+
f: 0x0c,
|
|
2894
|
+
n: 0x0a,
|
|
2895
|
+
r: 0x0d,
|
|
2896
|
+
t: 0x09,
|
|
2897
|
+
v: 0x0b,
|
|
2898
|
+
"\\": 0x5c,
|
|
2899
|
+
'"': 0x22,
|
|
2900
|
+
};
|
|
2901
|
+
|
|
2902
|
+
/**
|
|
2903
|
+
* Git quotes paths C-style: its named escapes plus exactly three octal digits
|
|
2904
|
+
* per byte (leading digit 0-3), so a multi-byte UTF-8 character arrives as
|
|
2905
|
+
* consecutive octal escapes. Anything else is malformed for this grammar.
|
|
2906
|
+
*/
|
|
2907
|
+
function parseDiffQuotedEscape(text: string, slashIndex: number): ParsedDiffQuotedEscape | undefined {
|
|
2908
|
+
const first = text.charAt(slashIndex + 1);
|
|
2909
|
+
if (first >= "0" && first <= "3") {
|
|
2910
|
+
const second = text.charAt(slashIndex + 2);
|
|
2911
|
+
const third = text.charAt(slashIndex + 3);
|
|
2912
|
+
if (second < "0" || second > "7" || third < "0" || third > "7") return undefined;
|
|
2913
|
+
return { byte: Number.parseInt(text.slice(slashIndex + 1, slashIndex + 4), 8), nextIndex: slashIndex + 4 };
|
|
2909
2914
|
}
|
|
2915
|
+
const named = DIFF_NAMED_ESCAPE_BYTES[first];
|
|
2916
|
+
if (named === undefined) return undefined;
|
|
2917
|
+
return { byte: named, nextIndex: slashIndex + 2 };
|
|
2910
2918
|
}
|
|
2911
2919
|
|
|
2920
|
+
/**
|
|
2921
|
+
* Escaped bytes are decoded as strict UTF-8. A token holding invalid UTF-8 or
|
|
2922
|
+
* a malformed escape keeps its escaped source form, so distinct non-UTF-8
|
|
2923
|
+
* byte sequences never collapse into one replacement-character path.
|
|
2924
|
+
*/
|
|
2912
2925
|
function parseDiffQuotedToken(text: string, startIndex: number): ParsedDiffHeaderToken | undefined {
|
|
2913
2926
|
if (text.charAt(startIndex) !== '"') return undefined;
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2927
|
+
const bytes: number[] = [];
|
|
2928
|
+
let malformed = false;
|
|
2929
|
+
for (let i = startIndex + 1; i < text.length; ) {
|
|
2930
|
+
const codePoint = text.codePointAt(i) as number;
|
|
2931
|
+
const ch = String.fromCodePoint(codePoint);
|
|
2932
|
+
if (ch === '"') {
|
|
2933
|
+
if (!malformed) {
|
|
2934
|
+
try {
|
|
2935
|
+
return { value: DIFF_PATH_DECODER.decode(Uint8Array.from(bytes)), nextIndex: i + 1 };
|
|
2936
|
+
} catch {}
|
|
2937
|
+
}
|
|
2938
|
+
return { value: text.slice(startIndex + 1, i), nextIndex: i + 1, escaped: true };
|
|
2939
|
+
}
|
|
2918
2940
|
if (ch !== "\\") {
|
|
2919
|
-
|
|
2941
|
+
bytes.push(...DIFF_PATH_ENCODER.encode(ch));
|
|
2942
|
+
i += ch.length;
|
|
2920
2943
|
continue;
|
|
2921
2944
|
}
|
|
2922
2945
|
const escaped = parseDiffQuotedEscape(text, i);
|
|
2923
|
-
|
|
2924
|
-
|
|
2946
|
+
if (escaped === undefined) {
|
|
2947
|
+
malformed = true;
|
|
2948
|
+
i += 2;
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
bytes.push(escaped.byte);
|
|
2952
|
+
i = escaped.nextIndex;
|
|
2925
2953
|
}
|
|
2926
2954
|
return undefined;
|
|
2927
2955
|
}
|
|
2928
2956
|
|
|
2957
|
+
function parseDiffPathLine(line: string, prefix: string): { value: string; escaped?: boolean } {
|
|
2958
|
+
const rest = line.slice(prefix.length);
|
|
2959
|
+
const token = parseDiffQuotedToken(rest, 0);
|
|
2960
|
+
return token ? { value: token.value, escaped: token.escaped } : { value: rest };
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2929
2963
|
function parseDiffHeaderToken(text: string, startIndex: number): ParsedDiffHeaderToken | undefined {
|
|
2930
2964
|
const start = skipDiffHeaderSpaces(text, startIndex);
|
|
2931
2965
|
if (start >= text.length) return undefined;
|
|
@@ -2940,7 +2974,14 @@ function stripPrDiffPathPrefix(value: string, prefix: "a/" | "b/"): string | und
|
|
|
2940
2974
|
return value.startsWith(prefix) ? value.slice(prefix.length) : undefined;
|
|
2941
2975
|
}
|
|
2942
2976
|
|
|
2943
|
-
|
|
2977
|
+
interface PrDiffHeaderPaths {
|
|
2978
|
+
oldPath?: string;
|
|
2979
|
+
newPath?: string;
|
|
2980
|
+
oldPathEscaped?: boolean;
|
|
2981
|
+
newPathEscaped?: boolean;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
function parsePrDiffHeaderPaths(header: string): PrDiffHeaderPaths {
|
|
2944
2985
|
const trail = header.slice("diff --git ".length);
|
|
2945
2986
|
if (trail.startsWith('"')) {
|
|
2946
2987
|
const oldToken = parseDiffQuotedToken(trail, 0);
|
|
@@ -2950,6 +2991,8 @@ function parsePrDiffHeaderPaths(header: string): { oldPath?: string; newPath?: s
|
|
|
2950
2991
|
return {
|
|
2951
2992
|
oldPath: stripPrDiffPathPrefix(oldToken.value, "a/"),
|
|
2952
2993
|
newPath: stripPrDiffPathPrefix(newToken.value, "b/"),
|
|
2994
|
+
oldPathEscaped: oldToken.escaped,
|
|
2995
|
+
newPathEscaped: newToken.escaped,
|
|
2953
2996
|
};
|
|
2954
2997
|
}
|
|
2955
2998
|
|
|
@@ -2980,6 +3023,8 @@ function parsePrDiffSection(section: string, startOffset: number, endOffset: num
|
|
|
2980
3023
|
const headerPaths = parsePrDiffHeaderPaths(header);
|
|
2981
3024
|
let oldPath = headerPaths.oldPath;
|
|
2982
3025
|
let newPath = headerPaths.newPath;
|
|
3026
|
+
let oldPathEscaped = headerPaths.oldPathEscaped;
|
|
3027
|
+
let newPathEscaped = headerPaths.newPathEscaped;
|
|
2983
3028
|
|
|
2984
3029
|
let changeType: PrDiffFile["changeType"] = "modified";
|
|
2985
3030
|
let isBinary = false;
|
|
@@ -2999,11 +3044,11 @@ function parsePrDiffSection(section: string, startOffset: number, endOffset: num
|
|
|
2999
3044
|
}
|
|
3000
3045
|
if (line.startsWith("rename from ")) {
|
|
3001
3046
|
changeType = "renamed";
|
|
3002
|
-
oldPath = line
|
|
3047
|
+
({ value: oldPath, escaped: oldPathEscaped } = parseDiffPathLine(line, "rename from "));
|
|
3003
3048
|
continue;
|
|
3004
3049
|
}
|
|
3005
3050
|
if (line.startsWith("rename to ")) {
|
|
3006
|
-
newPath = line
|
|
3051
|
+
({ value: newPath, escaped: newPathEscaped } = parseDiffPathLine(line, "rename to "));
|
|
3007
3052
|
continue;
|
|
3008
3053
|
}
|
|
3009
3054
|
if (line.startsWith("Binary files ") && line.endsWith(" differ")) {
|
|
@@ -3028,8 +3073,15 @@ function parsePrDiffSection(section: string, startOffset: number, endOffset: num
|
|
|
3028
3073
|
deletions = 0;
|
|
3029
3074
|
}
|
|
3030
3075
|
|
|
3031
|
-
const
|
|
3032
|
-
|
|
3076
|
+
const preferOld = changeType === "deleted";
|
|
3077
|
+
const displayPath = preferOld ? (oldPath ?? newPath ?? "(unknown)") : (newPath ?? oldPath ?? "(unknown)");
|
|
3078
|
+
const displayEscaped = preferOld
|
|
3079
|
+
? oldPath !== undefined
|
|
3080
|
+
? oldPathEscaped
|
|
3081
|
+
: newPathEscaped
|
|
3082
|
+
: newPath !== undefined
|
|
3083
|
+
? newPathEscaped
|
|
3084
|
+
: oldPathEscaped;
|
|
3033
3085
|
const file: PrDiffFile = {
|
|
3034
3086
|
path: displayPath,
|
|
3035
3087
|
additions,
|
|
@@ -3038,8 +3090,12 @@ function parsePrDiffSection(section: string, startOffset: number, endOffset: num
|
|
|
3038
3090
|
startOffset,
|
|
3039
3091
|
endOffset,
|
|
3040
3092
|
};
|
|
3041
|
-
if (
|
|
3093
|
+
if (displayEscaped) {
|
|
3094
|
+
file.pathEscaped = true;
|
|
3095
|
+
}
|
|
3096
|
+
if (oldPath && (oldPath !== displayPath || oldPathEscaped !== displayEscaped)) {
|
|
3042
3097
|
file.oldPath = oldPath;
|
|
3098
|
+
if (oldPathEscaped) file.oldPathEscaped = true;
|
|
3043
3099
|
}
|
|
3044
3100
|
return file;
|
|
3045
3101
|
}
|
|
@@ -103,7 +103,7 @@ export function openDb(): Database | null {
|
|
|
103
103
|
// than running an in-place ALTER dance.
|
|
104
104
|
const userVersion = (db.prepare("PRAGMA user_version").get() as { user_version?: number } | undefined)
|
|
105
105
|
?.user_version;
|
|
106
|
-
if (userVersion !== undefined && userVersion <
|
|
106
|
+
if (userVersion !== undefined && userVersion < 4) {
|
|
107
107
|
db.run("DROP TABLE IF EXISTS github_view_cache");
|
|
108
108
|
}
|
|
109
109
|
db.run(`
|
|
@@ -120,7 +120,7 @@ export function openDb(): Database | null {
|
|
|
120
120
|
PRIMARY KEY (auth_key, repo, kind, number, include_comments)
|
|
121
121
|
);
|
|
122
122
|
CREATE INDEX IF NOT EXISTS idx_github_view_cache_fetched ON github_view_cache(fetched_at);
|
|
123
|
-
PRAGMA user_version =
|
|
123
|
+
PRAGMA user_version = 4;
|
|
124
124
|
`);
|
|
125
125
|
protectDbFiles(dbPath);
|
|
126
126
|
cachedDb = db;
|
package/src/tools/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
} from "../session/agent-session";
|
|
18
18
|
import type { ArtifactManager } from "../session/artifacts";
|
|
19
19
|
import type { ClientBridge } from "../session/client-bridge";
|
|
20
|
+
import type { FoldAdapter } from "../session/fold-coordinator";
|
|
20
21
|
import type { CustomMessage } from "../session/messages";
|
|
21
22
|
import type { ToolChoiceQueue } from "../session/tool-choice-queue";
|
|
22
23
|
import type { SkillActiveEntry } from "../skill-state/active-state";
|
|
@@ -235,12 +236,13 @@ export interface ToolSession {
|
|
|
235
236
|
assertEvalExecutionAllowed?: () => void;
|
|
236
237
|
/** Track tool-owned eval work so session disposal can await/abort it like direct session eval runs. */
|
|
237
238
|
trackEvalExecution?<T>(execution: Promise<T>, abortController: AbortController): Promise<T>;
|
|
238
|
-
/** Register a
|
|
239
|
-
|
|
240
|
-
/** Whether a
|
|
239
|
+
/** Register a foldable foreground wait so the fold chord can move it into a background job. */
|
|
240
|
+
registerForegroundFoldParticipant?: (adapter: FoldAdapter) => () => void;
|
|
241
|
+
/** Whether a foreground wait is currently foldable into a background job. */
|
|
241
242
|
hasForegroundBashBackgroundRequestHandler?: () => boolean;
|
|
242
|
-
/** Request that the active
|
|
243
|
-
requestForegroundBashBackground?: () => boolean
|
|
243
|
+
/** Request that the active foreground wait fold into a background job, if supported. */
|
|
244
|
+
requestForegroundBashBackground?: () => Promise<boolean>;
|
|
245
|
+
|
|
244
246
|
/** Get the session-owned or inherited async job manager. */
|
|
245
247
|
getAsyncJobManager?: () => AsyncJobManager | undefined;
|
|
246
248
|
/** Resolves when the session queues user steering (or `signal` aborts) without consuming it; wait-style tools use this to end their observation early. */
|
|
@@ -269,6 +271,10 @@ export interface ToolSession {
|
|
|
269
271
|
bashAllowedPrefixes?: string[];
|
|
270
272
|
/** Restriction policy for sessions that deliberately expose a narrow bash surface. */
|
|
271
273
|
bashRestrictionProfile?: BashRestrictionProfile;
|
|
274
|
+
/** Process-local master capability injected into direct Bash children only. */
|
|
275
|
+
getMasterBashCapability?: () => string | undefined;
|
|
276
|
+
/** Stable master lineage identity used across session switches and branches. */
|
|
277
|
+
getMasterOwnerSessionId?: () => string | undefined;
|
|
272
278
|
/** Optional per-session allowlist for tools exposed through search_tool_bm25. */
|
|
273
279
|
discoverableToolAllowedNames?: readonly string[];
|
|
274
280
|
/** Throw instead of warn when toolNames contains an unknown name. */
|
|
@@ -2319,7 +2319,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2319
2319
|
"job": {
|
|
2320
2320
|
"name": "job",
|
|
2321
2321
|
"label": "Job",
|
|
2322
|
-
"description": "Inspects, waits, or cancels async jobs.\n\nBackground job results are delivered automatically when complete. Jobs that back task subagents should be controlled via the `subagent` tool when it is available; use `job` for non-subagent jobs (async bash, monitors) and as a compatibility fallback. Running job output stays quiet by default to avoid flooding the conversation; use `tail` when you explicitly want to show/reopen retained output. Reach for this tool only when you need to inspect or intervene.\n\nIn the interactive TUI,
|
|
2322
|
+
"description": "Inspects, waits, or cancels async jobs.\n\nBackground job results are delivered automatically when complete. Jobs that back task subagents should be controlled via the `subagent` tool when it is available; use `job` for non-subagent jobs (async bash, monitors) and as a compatibility fallback. Running job output stays quiet by default to avoid flooding the conversation; use `tail` when you explicitly want to show/reopen retained output. Reach for this tool only when you need to inspect or intervene.\n\nIn the interactive TUI, a running foreground command can be folded into a background job by pressing the fold chord twice (default `Alt+Shift+B`, plus `Cmd+B` on macOS; remappable via `app.tool.backgroundFold`). This covers managed non-PTY bash, editor/ACP client-terminal commands, and PTY-mode commands. A folded job keeps its original deadline, is never killed or restarted by folding, and its result wakes a later turn automatically; a folded PTY continues output-only and no longer accepts input. `task`/`subagent` waits are not foldable. Raw shell `Ctrl+Z`/`bg` is not the supported path inside GJC because it bypasses job ownership and output-routing contracts.\n\n# Operations\n\n## `list: true`\nUse to inspect what's running.\n\n## `tail: [id, …]`\nShow the retained output buffer for one or more background jobs without waiting.\n- Use this to reopen/tail a backgrounded long-running bash/tool output after folding it away.\n- Output is bounded by the manager retention window; stale cursors may report that only the retained tail is available.\n- Prefer `tail` over polling when you only need to peek at progress, so the conversation can continue without flooding the TUI.\n\n## `poll: [id, …]`\nBlock until the specified jobs finish or the wait window (~30 s, not configurable) elapses.\n- Use when you are genuinely blocked on a result and have no other work to do.\n- Returns the current snapshot when the timer elapses; running jobs remain running.\n- Completed jobs include their final output in the returned snapshot.\n\n## `cancel: [id, …]`\nStop running jobs.\n- Use when a job is stalled, hung, or no longer needed.\n- Returns immediately after cancelling.",
|
|
2323
2323
|
"parameters": {
|
|
2324
2324
|
"type": "object",
|
|
2325
2325
|
"properties": {
|
|
@@ -208,7 +208,15 @@ function chainCacheKey(
|
|
|
208
208
|
preferred ?? null,
|
|
209
209
|
fallbacks,
|
|
210
210
|
ctx
|
|
211
|
-
? [
|
|
211
|
+
? [
|
|
212
|
+
ctx.provider,
|
|
213
|
+
ctx.modelId,
|
|
214
|
+
ctx.wireModelId ?? null,
|
|
215
|
+
ctx.api,
|
|
216
|
+
ctx.baseUrl ?? null,
|
|
217
|
+
ctx.webSearch ?? null,
|
|
218
|
+
ctx.ownerAuthOverride ?? false,
|
|
219
|
+
]
|
|
212
220
|
: null,
|
|
213
221
|
]);
|
|
214
222
|
}
|
|
@@ -278,7 +286,17 @@ function isAnthropicWire(api: string): boolean {
|
|
|
278
286
|
}
|
|
279
287
|
|
|
280
288
|
function isGoogleWire(api: string): boolean {
|
|
281
|
-
return
|
|
289
|
+
return (
|
|
290
|
+
api === "google-generative-ai" ||
|
|
291
|
+
api === "google-generative-language" ||
|
|
292
|
+
api === "gemini-wire" ||
|
|
293
|
+
api === "google-vertex" ||
|
|
294
|
+
api === "google-gemini-cli"
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function isGenerativeLanguageWire(api: string): boolean {
|
|
299
|
+
return api === "google-generative-ai" || api === "google-generative-language" || api === "gemini-wire";
|
|
282
300
|
}
|
|
283
301
|
|
|
284
302
|
function isOpenAICompatWire(api: string): boolean {
|
|
@@ -305,6 +323,52 @@ function looksXaiFamilyModelId(ctx: ActiveSearchModelContext): boolean {
|
|
|
305
323
|
return looksXaiModelId(ctx.wireModelId) || looksXaiModelId(ctx.modelId);
|
|
306
324
|
}
|
|
307
325
|
|
|
326
|
+
const OFFICIAL_GEMINI_HOSTS = new Set([
|
|
327
|
+
"cloudcode-pa.googleapis.com",
|
|
328
|
+
"daily-cloudcode-pa.googleapis.com",
|
|
329
|
+
"daily-cloudcode-pa.sandbox.googleapis.com",
|
|
330
|
+
"generativelanguage.googleapis.com",
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
function normalizedHost(baseUrl: string): string | undefined {
|
|
334
|
+
try {
|
|
335
|
+
return new URL(baseUrl).hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "").replace(/\.$/, "");
|
|
336
|
+
} catch {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function isOfficialGeminiBaseUrl(baseUrl: string | undefined): boolean {
|
|
342
|
+
if (!baseUrl?.trim()) return true;
|
|
343
|
+
const host = normalizedHost(baseUrl);
|
|
344
|
+
return host !== undefined && OFFICIAL_GEMINI_HOSTS.has(host);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function isOfficialXaiBaseUrl(baseUrl: string | undefined): boolean {
|
|
348
|
+
if (!baseUrl?.trim()) return true;
|
|
349
|
+
return normalizedHost(baseUrl) === "api.x.ai";
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const CANONICAL_GEMINI_PROVIDERS = new Set(["google", "gemini", "google-gemini-cli", "google-antigravity"]);
|
|
353
|
+
|
|
354
|
+
function hasCustomGeminiSearchContext(ctx: ActiveSearchModelContext): boolean {
|
|
355
|
+
if (!isGoogleWire(ctx.api)) return false;
|
|
356
|
+
if (ctx.ownerAuthOverride) return true;
|
|
357
|
+
if (!isOfficialGeminiBaseUrl(ctx.baseUrl)) return true;
|
|
358
|
+
const provider = ctx.provider.toLowerCase();
|
|
359
|
+
return Boolean(ctx.resolveCredentials && !CANONICAL_GEMINI_PROVIDERS.has(provider));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function hasCustomXaiSearchContext(ctx: ActiveSearchModelContext): boolean {
|
|
363
|
+
if (!isOpenAICompatWire(ctx.api)) return false;
|
|
364
|
+
if (ctx.ownerAuthOverride) return true;
|
|
365
|
+
const provider = ctx.provider.toLowerCase();
|
|
366
|
+
if (provider === "xai" && !isOfficialXaiBaseUrl(ctx.baseUrl)) return true;
|
|
367
|
+
if (!looksXaiFamilyModelId(ctx)) return false;
|
|
368
|
+
if (!isOfficialXaiBaseUrl(ctx.baseUrl)) return true;
|
|
369
|
+
return provider !== "xai";
|
|
370
|
+
}
|
|
371
|
+
|
|
308
372
|
export function isLocalBaseUrl(baseUrl: string | undefined): boolean {
|
|
309
373
|
if (!baseUrl) return false;
|
|
310
374
|
let url: URL;
|
|
@@ -369,12 +433,29 @@ export function inferNativeProviderFromModel(ctx: ActiveSearchModelContext | und
|
|
|
369
433
|
if (!ctx || ctx.webSearch === "off") return undefined;
|
|
370
434
|
const modelId = (ctx.wireModelId ?? ctx.modelId).toLowerCase();
|
|
371
435
|
if (modelId.startsWith("claude-") && isAnthropicWire(ctx.api)) return "anthropic";
|
|
372
|
-
if (modelId.startsWith("gemini-") && isGoogleWire(ctx.api)) return "gemini";
|
|
373
|
-
|
|
436
|
+
if (modelId.startsWith("gemini-") && isGoogleWire(ctx.api) && !hasCustomGeminiSearchContext(ctx)) return "gemini";
|
|
437
|
+
// A custom proxy may expose a Grok model through an OpenAI wire while the
|
|
438
|
+
// process also has canonical xAI credentials. Do not infer the canonical
|
|
439
|
+
// xAI provider from the model family: that would silently send the search to
|
|
440
|
+
// api.x.ai with another registry's credentials. Custom active contexts are
|
|
441
|
+
// dispatched below by activeContextNativeId, where the provider can consume
|
|
442
|
+
// the active owner-bound resolver instead.
|
|
443
|
+
if (
|
|
444
|
+
looksXaiFamilyModelId(ctx) &&
|
|
445
|
+
isOpenAICompatWire(ctx.api) &&
|
|
446
|
+
ctx.provider.toLowerCase() === "xai" &&
|
|
447
|
+
!hasCustomXaiSearchContext(ctx)
|
|
448
|
+
)
|
|
449
|
+
return "xai";
|
|
374
450
|
// `codex` hits the ChatGPT backend with local Codex OAuth, so only infer it
|
|
375
451
|
// for genuine OpenAI endpoints. Custom/proxy OpenAI-compatible models fall
|
|
376
|
-
// through to `activeContextNativeId`
|
|
377
|
-
if (
|
|
452
|
+
// through to `activeContextNativeId` and reuse their own credentials.
|
|
453
|
+
if (
|
|
454
|
+
!ctx.ownerAuthOverride &&
|
|
455
|
+
looksOpenAIFamilyModelId(ctx) &&
|
|
456
|
+
isOpenAICompatWire(ctx.api) &&
|
|
457
|
+
isOpenAIOfficialBaseUrl(ctx.baseUrl)
|
|
458
|
+
) {
|
|
378
459
|
return "codex";
|
|
379
460
|
}
|
|
380
461
|
return undefined;
|
|
@@ -382,7 +463,10 @@ export function inferNativeProviderFromModel(ctx: ActiveSearchModelContext | und
|
|
|
382
463
|
|
|
383
464
|
function canUseDirectProviderMapping(ctx: ActiveSearchModelContext, id: SearchProviderId): boolean {
|
|
384
465
|
if (ctx.webSearch === "off") return false;
|
|
466
|
+
if (id === "gemini" && hasCustomGeminiSearchContext(ctx)) return false;
|
|
467
|
+
if (id === "xai" && hasCustomXaiSearchContext(ctx)) return false;
|
|
385
468
|
if (id !== "codex") return true;
|
|
469
|
+
if (ctx.ownerAuthOverride) return false;
|
|
386
470
|
// Same constraint as inference: the ChatGPT-backed codex provider is valid
|
|
387
471
|
// only for official OpenAI endpoints, not custom/proxy base URLs.
|
|
388
472
|
return isOpenAIOfficialBaseUrl(ctx.baseUrl);
|
|
@@ -395,6 +479,10 @@ export async function canUseGenericCredentials(
|
|
|
395
479
|
signal?: AbortSignal,
|
|
396
480
|
): Promise<boolean> {
|
|
397
481
|
if (!ctx) return false;
|
|
482
|
+
if (ctx.resolveCredentials) {
|
|
483
|
+
const resolved = await ctx.resolveCredentials({ sessionId, signal });
|
|
484
|
+
return Boolean(resolved.apiKey) || hasActiveCredentialHeader(resolved.headers);
|
|
485
|
+
}
|
|
398
486
|
const key = await authStorage.getApiKey(ctx.provider, sessionId, {
|
|
399
487
|
baseUrl: ctx.baseUrl,
|
|
400
488
|
modelId: ctx.modelId,
|
|
@@ -403,6 +491,13 @@ export async function canUseGenericCredentials(
|
|
|
403
491
|
return Boolean(key);
|
|
404
492
|
}
|
|
405
493
|
|
|
494
|
+
function hasActiveCredentialHeader(headers: Record<string, string> | undefined): boolean {
|
|
495
|
+
return Object.keys(headers ?? {}).some(key => {
|
|
496
|
+
const normalized = key.toLowerCase();
|
|
497
|
+
return normalized === "authorization" || normalized === "x-api-key" || normalized === "x-goog-api-key";
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
406
501
|
/**
|
|
407
502
|
* Native web-search provider to attempt by reusing the ACTIVE model's own
|
|
408
503
|
* credentials + baseUrl, dispatched by the model's wire protocol.
|
|
@@ -424,13 +519,20 @@ export function activeContextNativeId(ctx: ActiveSearchModelContext | undefined)
|
|
|
424
519
|
if (!ctx || ctx.webSearch === "off") return undefined;
|
|
425
520
|
const modelId = (ctx.wireModelId ?? ctx.modelId).toLowerCase();
|
|
426
521
|
// Dispatch must match exactly what each provider can service by reusing the
|
|
427
|
-
// active credential:
|
|
428
|
-
// OpenAI
|
|
429
|
-
//
|
|
430
|
-
//
|
|
522
|
+
// active credential: xAI-family models use the xAI search tools, the
|
|
523
|
+
// OpenAI-compatible adapter only speaks the two plain OpenAI wires (not
|
|
524
|
+
// azure), and the Gemini active path only speaks the public Generative
|
|
525
|
+
// Language wire (not vertex/cloud-code). Returning an id the provider would
|
|
526
|
+
// reject just wastes a guaranteed-fail attempt before DuckDuckGo.
|
|
431
527
|
if (isAnthropicWire(ctx.api) && modelId.startsWith("claude-")) return "anthropic";
|
|
528
|
+
if (isGenerativeLanguageWire(ctx.api) && modelId.startsWith("gemini-")) return "gemini";
|
|
529
|
+
if (
|
|
530
|
+
isOpenAICompatWire(ctx.api) &&
|
|
531
|
+
hasCustomXaiSearchContext(ctx) &&
|
|
532
|
+
(looksXaiFamilyModelId(ctx) || ctx.provider.toLowerCase() === "xai")
|
|
533
|
+
)
|
|
534
|
+
return "xai";
|
|
432
535
|
if (ctx.api === "openai-responses" || ctx.api === "openai-completions") return "openai-compatible";
|
|
433
|
-
if (ctx.api === "google-generative-ai" && modelId.startsWith("gemini-")) return "gemini";
|
|
434
536
|
return undefined;
|
|
435
537
|
}
|
|
436
538
|
|
|
@@ -445,6 +547,13 @@ export async function resolveProviderChain(options: ResolveProviderChainOptions)
|
|
|
445
547
|
settings,
|
|
446
548
|
} = options;
|
|
447
549
|
|
|
550
|
+
// Resolve the active model before consulting the chain cache. The registry
|
|
551
|
+
// resolver refreshes rotating apiKeyEnv credentials and updates generated
|
|
552
|
+
// headers, which also advances AuthStorage's generation when credentials
|
|
553
|
+
// rotate or disappear.
|
|
554
|
+
if (preferredProvider === "auto" && activeModelContext?.resolveCredentials) {
|
|
555
|
+
await activeModelContext.resolveCredentials({ sessionId, signal });
|
|
556
|
+
}
|
|
448
557
|
const cacheKey = chainCacheKey(preferredProvider, fallbackProviders, activeModelContext, authStorage, settings);
|
|
449
558
|
const perStorage = chainCache.get(authStorage);
|
|
450
559
|
const now = Date.now();
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "@gajae-code/ai/utils/anthropic-auth";
|
|
19
19
|
import { $credentialEnv, $env } from "@gajae-code/utils";
|
|
20
20
|
import type {
|
|
21
|
+
ActiveSearchModelCredentials,
|
|
21
22
|
AnthropicApiResponse,
|
|
22
23
|
AnthropicCitation,
|
|
23
24
|
SearchCitation,
|
|
@@ -73,6 +74,28 @@ function buildSystemBlocks(
|
|
|
73
74
|
});
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
function mergeHeadersPreservingExplicit(
|
|
78
|
+
baseHeaders: Record<string, string>,
|
|
79
|
+
explicitHeaders: Record<string, string> | undefined,
|
|
80
|
+
): Record<string, string> {
|
|
81
|
+
const merged = { ...baseHeaders };
|
|
82
|
+
for (const [key, value] of Object.entries(explicitHeaders ?? {})) {
|
|
83
|
+
for (const existingKey of Object.keys(merged)) {
|
|
84
|
+
if (existingKey.toLowerCase() === key.toLowerCase()) delete merged[existingKey];
|
|
85
|
+
}
|
|
86
|
+
merged[key] = value;
|
|
87
|
+
}
|
|
88
|
+
return merged;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function explicitCredentialHeader(headers: Record<string, string> | undefined): string | undefined {
|
|
92
|
+
for (const [key, value] of Object.entries(headers ?? {})) {
|
|
93
|
+
const normalized = key.toLowerCase();
|
|
94
|
+
if (normalized === "authorization" || normalized === "x-api-key") return value;
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
76
99
|
/**
|
|
77
100
|
* Calls the Anthropic API with web search tool enabled.
|
|
78
101
|
* @param auth - Authentication configuration (API key or OAuth)
|
|
@@ -93,7 +116,7 @@ async function callSearch(
|
|
|
93
116
|
extraHeaders?: Record<string, string>,
|
|
94
117
|
): Promise<AnthropicApiResponse> {
|
|
95
118
|
const url = buildAnthropicUrl(auth);
|
|
96
|
-
const headers =
|
|
119
|
+
const headers = mergeHeadersPreservingExplicit(buildAnthropicSearchHeaders(auth), extraHeaders);
|
|
97
120
|
|
|
98
121
|
const systemBlocks = buildSystemBlocks(auth, model, systemPrompt);
|
|
99
122
|
|
|
@@ -284,29 +307,78 @@ export async function searchAnthropic(
|
|
|
284
307
|
// proxy), prefer its wire model id and carry its request headers through.
|
|
285
308
|
let modelOverride: string | undefined;
|
|
286
309
|
let extraHeaders: Record<string, string> | undefined;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
310
|
+
const activeContext = "authStorage" in params ? params.activeModelContext : undefined;
|
|
311
|
+
const activeSessionId = "authStorage" in params ? params.sessionId : undefined;
|
|
312
|
+
const activeCustomContext =
|
|
313
|
+
activeContext?.api === "anthropic-messages" &&
|
|
314
|
+
(activeContext.ownerAuthOverride || activeContext.provider.toLowerCase() !== "anthropic");
|
|
315
|
+
|
|
316
|
+
if (activeCustomContext && activeContext?.resolveCredentials) {
|
|
317
|
+
const activeCredentials = await activeContext.resolveCredentials({
|
|
318
|
+
sessionId: activeSessionId,
|
|
292
319
|
signal: params.signal,
|
|
293
320
|
});
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
321
|
+
const ctxKey =
|
|
322
|
+
activeCredentials.apiKey ?? explicitCredentialHeader(activeCredentials.headers ?? activeContext.headers);
|
|
323
|
+
if (ctxKey) {
|
|
324
|
+
auth = buildAnthropicAuthConfig(ctxKey, activeContext.baseUrl);
|
|
325
|
+
modelOverride = activeContext.wireModelId ?? activeContext.modelId;
|
|
326
|
+
extraHeaders = activeCredentials.headers ?? activeContext.headers;
|
|
327
|
+
}
|
|
328
|
+
} else if (searchApiKey) {
|
|
329
|
+
auth = buildAnthropicAuthConfig(searchApiKey, searchBaseUrl);
|
|
330
|
+
} else if ("authStorage" in params) {
|
|
331
|
+
const ctx = activeContext;
|
|
332
|
+
const resolveActiveCredentials = ctx?.resolveCredentials;
|
|
333
|
+
const activeAnthropicWire = ctx?.api === "anthropic-messages";
|
|
334
|
+
const activeModelOwnsProvider =
|
|
335
|
+
ctx !== undefined &&
|
|
336
|
+
resolveActiveCredentials !== undefined &&
|
|
337
|
+
activeAnthropicWire &&
|
|
338
|
+
ctx.provider.toLowerCase() === "anthropic";
|
|
339
|
+
const activeCustomModel =
|
|
340
|
+
ctx !== undefined &&
|
|
341
|
+
resolveActiveCredentials !== undefined &&
|
|
342
|
+
activeAnthropicWire &&
|
|
343
|
+
ctx.provider.toLowerCase() !== "anthropic";
|
|
344
|
+
if (activeModelOwnsProvider || activeCustomModel) {
|
|
345
|
+
const activeCredentials: ActiveSearchModelCredentials = await resolveActiveCredentials({
|
|
346
|
+
sessionId: params.sessionId,
|
|
304
347
|
signal: params.signal,
|
|
305
348
|
});
|
|
349
|
+
const ctxKey = activeCredentials.apiKey ?? explicitCredentialHeader(activeCredentials.headers ?? ctx.headers);
|
|
306
350
|
if (ctxKey) {
|
|
307
351
|
auth = buildAnthropicAuthConfig(ctxKey, ctx.baseUrl);
|
|
308
352
|
modelOverride = ctx.wireModelId ?? ctx.modelId;
|
|
309
|
-
extraHeaders = ctx.headers;
|
|
353
|
+
extraHeaders = activeCredentials.headers ?? ctx.headers;
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
const apiKey = await params.authStorage.getApiKey("anthropic", params.sessionId, {
|
|
357
|
+
signal: params.signal,
|
|
358
|
+
});
|
|
359
|
+
if (apiKey) auth = buildAnthropicAuthConfig(apiKey);
|
|
360
|
+
|
|
361
|
+
// Fall back to the active model's own credentials + baseUrl when no
|
|
362
|
+
// canonical Anthropic key exists but the active model speaks the
|
|
363
|
+
// Anthropic wire (e.g. Claude served through a proxy).
|
|
364
|
+
if (!auth && ctx && ctx.api === "anthropic-messages") {
|
|
365
|
+
const activeCredentials: ActiveSearchModelCredentials = ctx.resolveCredentials
|
|
366
|
+
? await ctx.resolveCredentials({ sessionId: params.sessionId, signal: params.signal })
|
|
367
|
+
: {
|
|
368
|
+
apiKey: await params.authStorage.getApiKey(ctx.provider, params.sessionId, {
|
|
369
|
+
baseUrl: ctx.baseUrl,
|
|
370
|
+
modelId: ctx.modelId,
|
|
371
|
+
signal: params.signal,
|
|
372
|
+
}),
|
|
373
|
+
headers: ctx.headers,
|
|
374
|
+
};
|
|
375
|
+
const ctxKey =
|
|
376
|
+
activeCredentials.apiKey ?? explicitCredentialHeader(activeCredentials.headers ?? ctx.headers);
|
|
377
|
+
if (ctxKey) {
|
|
378
|
+
auth = buildAnthropicAuthConfig(ctxKey, ctx.baseUrl);
|
|
379
|
+
modelOverride = ctx.wireModelId ?? ctx.modelId;
|
|
380
|
+
extraHeaders = activeCredentials.headers ?? ctx.headers;
|
|
381
|
+
}
|
|
310
382
|
}
|
|
311
383
|
}
|
|
312
384
|
}
|