@gajae-code/ai 0.16.7 → 0.17.1

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/dist/types/auth-storage.d.ts +23 -4
  3. package/dist/types/models.d.ts +14 -0
  4. package/dist/types/provider-models/special.d.ts +12 -0
  5. package/dist/types/providers/anthropic.d.ts +1 -1
  6. package/dist/types/providers/cursor.d.ts +33 -21
  7. package/dist/types/providers/devin-acp.d.ts +157 -0
  8. package/dist/types/providers/google-gemini-headers.d.ts +1 -1
  9. package/dist/types/providers/mock.d.ts +2 -0
  10. package/dist/types/providers/openai-responses-shared.d.ts +21 -1
  11. package/dist/types/providers/register-builtins.d.ts +1 -0
  12. package/dist/types/types.d.ts +52 -11
  13. package/dist/types/utils/block-symbols.d.ts +15 -5
  14. package/dist/types/utils/fallback-transport.d.ts +4 -1
  15. package/dist/types/utils.d.ts +13 -0
  16. package/package.json +4 -3
  17. package/src/api-registry.ts +1 -0
  18. package/src/auth-broker/redact.ts +10 -2
  19. package/src/auth-gateway/server.ts +56 -3
  20. package/src/auth-storage.ts +330 -116
  21. package/src/model-manager.ts +21 -2
  22. package/src/models.d.ts +14 -0
  23. package/src/models.json +117 -0
  24. package/src/models.ts +18 -0
  25. package/src/provider-models/descriptors.ts +7 -0
  26. package/src/provider-models/openai-compat.ts +14 -0
  27. package/src/provider-models/special.ts +39 -0
  28. package/src/providers/anthropic.d.ts +1 -1
  29. package/src/providers/anthropic.ts +1 -1
  30. package/src/providers/azure-openai-responses.ts +10 -1
  31. package/src/providers/cursor.d.ts +33 -21
  32. package/src/providers/cursor.ts +2024 -508
  33. package/src/providers/devin-acp.d.ts +157 -0
  34. package/src/providers/devin-acp.ts +1103 -0
  35. package/src/providers/google-gemini-headers.d.ts +1 -1
  36. package/src/providers/google-gemini-headers.ts +1 -1
  37. package/src/providers/mock.ts +16 -1
  38. package/src/providers/openai-chat-server.ts +3 -3
  39. package/src/providers/openai-codex-responses.ts +27 -17
  40. package/src/providers/openai-responses-server.ts +5 -5
  41. package/src/providers/openai-responses-shared.d.ts +21 -1
  42. package/src/providers/openai-responses-shared.ts +60 -6
  43. package/src/providers/openai-responses.ts +10 -1
  44. package/src/providers/register-builtins.d.ts +1 -0
  45. package/src/providers/register-builtins.ts +21 -1
  46. package/src/stream.ts +14 -0
  47. package/src/types.d.ts +52 -11
  48. package/src/types.ts +70 -8
  49. package/src/utils/block-symbols.d.ts +15 -5
  50. package/src/utils/block-symbols.ts +16 -6
  51. package/src/utils/discovery/cursor.ts +3 -2
  52. package/src/utils/fallback-transport.d.ts +4 -1
  53. package/src/utils/fallback-transport.ts +12 -5
  54. package/src/utils.d.ts +13 -0
  55. package/src/utils.ts +17 -0
  56. package/dist/types/utils/codex-entitlement.d.ts +0 -22
  57. package/src/utils/codex-entitlement.d.ts +0 -22
  58. package/src/utils/codex-entitlement.ts +0 -57
package/src/types.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { AnthropicOptions } from "./providers/anthropic";
4
4
  import type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses";
5
5
  import type { CursorOptions } from "./providers/cursor";
6
6
  import type { DeleteArgs, DeleteResult, DiagnosticsArgs, DiagnosticsResult, GrepArgs, GrepResult, LsArgs, LsResult, McpResult, PiBashExecArgs, PiBashExecResult, PiEditExecArgs, PiEditExecResult, PiFindExecArgs, PiFindExecResult, PiGrepExecArgs, PiGrepExecResult, PiLsExecArgs, PiLsExecResult, PiReadExecArgs, PiReadExecResult, PiWriteExecArgs, PiWriteExecResult, ReadArgs, ReadResult, ShellArgs, ShellResult, WriteArgs, WriteResult } from "./providers/cursor/gen/agent_pb";
7
+ import type { DevinAcpConfig, DevinAcpOptions } from "./providers/devin-acp";
7
8
  import type { GoogleOptions } from "./providers/google";
8
9
  import type { GoogleGeminiCliOptions } from "./providers/google-gemini-cli";
9
10
  import type { GoogleVertexOptions } from "./providers/google-vertex";
@@ -16,7 +17,7 @@ import type { AssistantMessageEventStream } from "./utils/event-stream";
16
17
  import type { FallbackAttemptToken, TransportFailureFacts } from "./utils/fallback-transport";
17
18
  import type { UnicodeEscapeEvidence } from "./utils/json-parse";
18
19
  export type { AssistantMessageEventStream } from "./utils/event-stream";
19
- export type KnownApi = "openai-completions" | "openai-responses" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" | "google-gemini-cli" | "google-vertex" | "ollama-chat" | "cursor-agent" | "kiro-codewhisperer-stream";
20
+ export type KnownApi = "openai-completions" | "openai-responses" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" | "google-gemini-cli" | "google-vertex" | "ollama-chat" | "cursor-agent" | "devin-acp" | "kiro-codewhisperer-stream";
20
21
  export type Api = KnownApi | (string & {});
21
22
  export interface ApiOptionsMap {
22
23
  "anthropic-messages": AnthropicOptions;
@@ -30,6 +31,7 @@ export interface ApiOptionsMap {
30
31
  "google-vertex": GoogleVertexOptions;
31
32
  "ollama-chat": OllamaChatOptions;
32
33
  "cursor-agent": CursorOptions;
34
+ "devin-acp": DevinAcpOptions;
33
35
  "kiro-codewhisperer-stream": KiroCodeWhispererOptions;
34
36
  }
35
37
  export type OptionsForApi<TApi extends Api> = StreamOptions | (TApi extends keyof ApiOptionsMap ? ApiOptionsMap[TApi] : never);
@@ -54,7 +56,7 @@ export interface ThinkingConfig {
54
56
  /** Provider-specific transport used to encode the selected effort. */
55
57
  mode: ThinkingControlMode;
56
58
  }
57
- export declare const KNOWN_PROVIDERS: readonly ["alibaba-token-plan", "amazon-bedrock", "kiro", "azure-openai", "anthropic", "google", "google-gemini-cli", "google-antigravity", "google-vertex", "openai", "openai-codex", "opencodex", "kimi-code", "minimax-code", "minimax-code-cn", "github-copilot", "fireworks", "firepass", "fugu", "gitlab-duo", "cursor", "jetbrains-junie", "deepseek", "deepinfra", "xai", "groq", "cerebras", "openrouter", "kilo", "vercel-ai-gateway", "zai", "glm-zcode", "mistral", "minimax", "opencode-go", "commandcode-goat", "opencode-zen", "opengateway", "bizrouter", "mara", "synthetic", "cloudflare-ai-gateway", "huggingface", "litellm", "moonshot", "nvidia", "nanogpt", "ollama", "ollama-cloud", "qianfan", "qwen-portal", "sglang", "together", "venice", "vllm", "xiaomi", "xiaomi-token-plan-sgp", "xiaomi-token-plan-ams", "xiaomi-token-plan-cn", "zenmux", "lm-studio", "omlx"];
59
+ export declare const KNOWN_PROVIDERS: readonly ["alibaba-token-plan", "amazon-bedrock", "kiro", "azure-openai", "anthropic", "google", "google-gemini-cli", "google-antigravity", "google-vertex", "openai", "openai-codex", "opencodex", "kimi-code", "minimax-code", "minimax-code-cn", "github-copilot", "fireworks", "firepass", "fugu", "gitlab-duo", "cursor", "devin", "jetbrains-junie", "deepseek", "deepinfra", "xai", "groq", "cerebras", "openrouter", "kilo", "vercel-ai-gateway", "zai", "glm-zcode", "mistral", "minimax", "opencode-go", "commandcode-goat", "opencode-zen", "opengateway", "bizrouter", "mara", "synthetic", "cloudflare-ai-gateway", "huggingface", "litellm", "moonshot", "nvidia", "nanogpt", "ollama", "ollama-cloud", "qianfan", "qwen-portal", "sglang", "together", "venice", "vllm", "xiaomi", "xiaomi-token-plan-sgp", "xiaomi-token-plan-ams", "xiaomi-token-plan-cn", "zenmux", "lm-studio", "omlx"];
58
60
  export type KnownProvider = (typeof KNOWN_PROVIDERS)[number];
59
61
  export declare function isKnownProvider(provider: string): provider is KnownProvider;
60
62
  export type Provider = KnownProvider | string;
@@ -251,6 +253,13 @@ export interface StreamOptions {
251
253
  * Providers can use this to persist transport/session state between turns.
252
254
  */
253
255
  providerSessionState?: Map<string, ProviderSessionState>;
256
+ /**
257
+ * Set by GJC for internal maintenance/one-shot work (context compaction,
258
+ * handoff and branch summaries, utility generations) rather than an
259
+ * interactive user turn. Agent-level providers use it to refuse requests they
260
+ * cannot serve instead of forwarding them to a billed upstream agent.
261
+ */
262
+ maintenanceCall?: boolean;
254
263
  /**
255
264
  * Optional callback for inspecting or replacing provider payloads before sending.
256
265
  * Return undefined to keep the payload unchanged.
@@ -306,6 +315,12 @@ export interface StreamOptions {
306
315
  authCredentialType?: "api_key" | "oauth";
307
316
  /** Cursor exec/MCP tool handlers (cursor-agent only). */
308
317
  execHandlers?: CursorExecHandlers;
318
+ /**
319
+ * Devin CLI ACP provider configuration (devin-acp only). When absent, the
320
+ * provider spawns `devin acp` from PATH (or `GJC_DEVIN_CLI_PATH`) in the
321
+ * current working directory and applies the default permission policy.
322
+ */
323
+ devinAcp?: DevinAcpConfig;
309
324
  /** Per-attempt identity for execution attribution. Threaded into onPayload/onResponse calls. */
310
325
  attemptScope?: AttemptScopeRef;
311
326
  }
@@ -550,6 +565,14 @@ export interface AssistantMessage {
550
565
  usage: Usage;
551
566
  stopReason: StopReason;
552
567
  errorMessage?: string;
568
+ /**
569
+ * Bounded, redaction-safe failure classifier for a terminal provider/runtime
570
+ * failure (a safe token matching `[A-Za-z0-9._-]{1,64}`), e.g.
571
+ * `upstream_stream_interrupted`. Set by the provider/agent that owns the
572
+ * classifier; never raw provider text and never a retry-admission fact (retry
573
+ * policy keys on `transportFailure`, not on this diagnostic).
574
+ */
575
+ errorCode?: string;
553
576
  errorKind?: AssistantErrorKind;
554
577
  /**
555
578
  * Structured, shape-only diagnostic for a terminal local staging-buffer
@@ -612,17 +635,35 @@ export interface CursorShellStreamCallbacks {
612
635
  export interface CursorPiCall<TArgs> {
613
636
  args: TArgs;
614
637
  toolCallId: string;
638
+ /** Per-exec cancellation signal; aborted when the caller aborts or the local exec deadline fires. */
639
+ signal?: AbortSignal;
640
+ /** Marks a started mutation that must settle before Cursor can terminalize the exec. */
641
+ markNonAbortable?: () => void;
615
642
  }
616
643
  export interface CursorExecHandlers {
617
- read?: (args: ReadArgs) => Promise<CursorExecHandlerResult<ReadResult>>;
618
- ls?: (args: LsArgs) => Promise<CursorExecHandlerResult<LsResult>>;
619
- grep?: (args: GrepArgs) => Promise<CursorExecHandlerResult<GrepResult>>;
620
- write?: (args: WriteArgs) => Promise<CursorExecHandlerResult<WriteResult>>;
621
- delete?: (args: DeleteArgs) => Promise<CursorExecHandlerResult<DeleteResult>>;
622
- shell?: (args: ShellArgs) => Promise<CursorExecHandlerResult<ShellResult>>;
623
- shellStream?: (args: ShellArgs, callbacks: CursorShellStreamCallbacks) => Promise<CursorExecHandlerResult<ShellResult>>;
624
- diagnostics?: (args: DiagnosticsArgs) => Promise<CursorExecHandlerResult<DiagnosticsResult>>;
625
- mcp?: (call: CursorMcpCall) => Promise<CursorExecHandlerResult<McpResult>>;
644
+ read?: (args: ReadArgs, signal?: AbortSignal,
645
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
646
+ markNonAbortable?: () => void) => Promise<CursorExecHandlerResult<ReadResult>>;
647
+ ls?: (args: LsArgs, signal?: AbortSignal,
648
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
649
+ markNonAbortable?: () => void) => Promise<CursorExecHandlerResult<LsResult>>;
650
+ grep?: (args: GrepArgs, signal?: AbortSignal) => Promise<CursorExecHandlerResult<GrepResult>>;
651
+ write?: (args: WriteArgs, signal?: AbortSignal,
652
+ /** Marks a started non-abortable mutation so the exec terminal waits for settlement. */
653
+ markNonAbortable?: () => void) => Promise<CursorExecHandlerResult<WriteResult>>;
654
+ delete?: (args: DeleteArgs, signal?: AbortSignal,
655
+ /** Marks a started non-abortable mutation so the exec terminal waits for settlement. */
656
+ markNonAbortable?: () => void) => Promise<CursorExecHandlerResult<DeleteResult>>;
657
+ shell?: (args: ShellArgs, signal?: AbortSignal,
658
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
659
+ markNonAbortable?: () => void) => Promise<CursorExecHandlerResult<ShellResult>>;
660
+ shellStream?: (args: ShellArgs, callbacks: CursorShellStreamCallbacks, signal?: AbortSignal,
661
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
662
+ markNonAbortable?: () => void) => Promise<CursorExecHandlerResult<ShellResult>>;
663
+ diagnostics?: (args: DiagnosticsArgs, signal?: AbortSignal) => Promise<CursorExecHandlerResult<DiagnosticsResult>>;
664
+ mcp?: (call: CursorMcpCall, signal?: AbortSignal,
665
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
666
+ markNonAbortable?: () => void) => Promise<CursorExecHandlerResult<McpResult>>;
626
667
  piRead?: (call: CursorPiCall<PiReadExecArgs>) => Promise<CursorExecHandlerResult<PiReadExecResult>>;
627
668
  piBash?: (call: CursorPiCall<PiBashExecArgs>) => Promise<CursorExecHandlerResult<PiBashExecResult>>;
628
669
  piEdit?: (call: CursorPiCall<PiEditExecArgs>) => Promise<CursorExecHandlerResult<PiEditExecResult>>;
package/src/types.ts CHANGED
@@ -34,6 +34,7 @@ import type {
34
34
  WriteArgs,
35
35
  WriteResult,
36
36
  } from "./providers/cursor/gen/agent_pb";
37
+ import type { DevinAcpConfig, DevinAcpOptions } from "./providers/devin-acp";
37
38
  import type { GoogleOptions } from "./providers/google";
38
39
  import type { GoogleGeminiCliOptions } from "./providers/google-gemini-cli";
39
40
  import type { GoogleVertexOptions } from "./providers/google-vertex";
@@ -60,6 +61,7 @@ export type KnownApi =
60
61
  | "google-vertex"
61
62
  | "ollama-chat"
62
63
  | "cursor-agent"
64
+ | "devin-acp"
63
65
  | "kiro-codewhisperer-stream";
64
66
  export type Api = KnownApi | (string & {});
65
67
  export interface ApiOptionsMap {
@@ -74,6 +76,7 @@ export interface ApiOptionsMap {
74
76
  "google-vertex": GoogleVertexOptions;
75
77
  "ollama-chat": OllamaChatOptions;
76
78
  "cursor-agent": CursorOptions;
79
+ "devin-acp": DevinAcpOptions;
77
80
  "kiro-codewhisperer-stream": KiroCodeWhispererOptions;
78
81
  }
79
82
  // Compile-time exhaustiveness check - this will fail if ApiOptionsMap doesn't have all KnownApi keys
@@ -153,6 +156,7 @@ export const KNOWN_PROVIDERS = [
153
156
  "fugu",
154
157
  "gitlab-duo",
155
158
  "cursor",
159
+ "devin",
156
160
  "jetbrains-junie",
157
161
  "deepseek",
158
162
  "deepinfra",
@@ -455,6 +459,13 @@ export interface StreamOptions {
455
459
  * Providers can use this to persist transport/session state between turns.
456
460
  */
457
461
  providerSessionState?: Map<string, ProviderSessionState>;
462
+ /**
463
+ * Set by GJC for internal maintenance/one-shot work (context compaction,
464
+ * handoff and branch summaries, utility generations) rather than an
465
+ * interactive user turn. Agent-level providers use it to refuse requests they
466
+ * cannot serve instead of forwarding them to a billed upstream agent.
467
+ */
468
+ maintenanceCall?: boolean;
458
469
  /**
459
470
  * Optional callback for inspecting or replacing provider payloads before sending.
460
471
  * Return undefined to keep the payload unchanged.
@@ -520,6 +531,12 @@ export interface StreamOptions {
520
531
  authCredentialType?: "api_key" | "oauth";
521
532
  /** Cursor exec/MCP tool handlers (cursor-agent only). */
522
533
  execHandlers?: CursorExecHandlers;
534
+ /**
535
+ * Devin CLI ACP provider configuration (devin-acp only). When absent, the
536
+ * provider spawns `devin acp` from PATH (or `GJC_DEVIN_CLI_PATH`) in the
537
+ * current working directory and applies the default permission policy.
538
+ */
539
+ devinAcp?: DevinAcpConfig;
523
540
  /** Per-attempt identity for execution attribution. Threaded into onPayload/onResponse calls. */
524
541
  attemptScope?: AttemptScopeRef;
525
542
  }
@@ -786,6 +803,14 @@ export interface AssistantMessage {
786
803
  usage: Usage;
787
804
  stopReason: StopReason;
788
805
  errorMessage?: string;
806
+ /**
807
+ * Bounded, redaction-safe failure classifier for a terminal provider/runtime
808
+ * failure (a safe token matching `[A-Za-z0-9._-]{1,64}`), e.g.
809
+ * `upstream_stream_interrupted`. Set by the provider/agent that owns the
810
+ * classifier; never raw provider text and never a retry-admission fact (retry
811
+ * policy keys on `transportFailure`, not on this diagnostic).
812
+ */
813
+ errorCode?: string;
789
814
  errorKind?: AssistantErrorKind;
790
815
  /**
791
816
  * Structured, shape-only diagnostic for a terminal local staging-buffer
@@ -854,21 +879,58 @@ export interface CursorShellStreamCallbacks {
854
879
  export interface CursorPiCall<TArgs> {
855
880
  args: TArgs;
856
881
  toolCallId: string;
882
+ /** Per-exec cancellation signal; aborted when the caller aborts or the local exec deadline fires. */
883
+ signal?: AbortSignal;
884
+ /** Marks a started mutation that must settle before Cursor can terminalize the exec. */
885
+ markNonAbortable?: () => void;
857
886
  }
858
887
 
859
888
  export interface CursorExecHandlers {
860
- read?: (args: ReadArgs) => Promise<CursorExecHandlerResult<ReadResult>>;
861
- ls?: (args: LsArgs) => Promise<CursorExecHandlerResult<LsResult>>;
862
- grep?: (args: GrepArgs) => Promise<CursorExecHandlerResult<GrepResult>>;
863
- write?: (args: WriteArgs) => Promise<CursorExecHandlerResult<WriteResult>>;
864
- delete?: (args: DeleteArgs) => Promise<CursorExecHandlerResult<DeleteResult>>;
865
- shell?: (args: ShellArgs) => Promise<CursorExecHandlerResult<ShellResult>>;
889
+ read?: (
890
+ args: ReadArgs,
891
+ signal?: AbortSignal,
892
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
893
+ markNonAbortable?: () => void,
894
+ ) => Promise<CursorExecHandlerResult<ReadResult>>;
895
+ ls?: (
896
+ args: LsArgs,
897
+ signal?: AbortSignal,
898
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
899
+ markNonAbortable?: () => void,
900
+ ) => Promise<CursorExecHandlerResult<LsResult>>;
901
+ grep?: (args: GrepArgs, signal?: AbortSignal) => Promise<CursorExecHandlerResult<GrepResult>>;
902
+ write?: (
903
+ args: WriteArgs,
904
+ signal?: AbortSignal,
905
+ /** Marks a started non-abortable mutation so the exec terminal waits for settlement. */
906
+ markNonAbortable?: () => void,
907
+ ) => Promise<CursorExecHandlerResult<WriteResult>>;
908
+ delete?: (
909
+ args: DeleteArgs,
910
+ signal?: AbortSignal,
911
+ /** Marks a started non-abortable mutation so the exec terminal waits for settlement. */
912
+ markNonAbortable?: () => void,
913
+ ) => Promise<CursorExecHandlerResult<DeleteResult>>;
914
+ shell?: (
915
+ args: ShellArgs,
916
+ signal?: AbortSignal,
917
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
918
+ markNonAbortable?: () => void,
919
+ ) => Promise<CursorExecHandlerResult<ShellResult>>;
866
920
  shellStream?: (
867
921
  args: ShellArgs,
868
922
  callbacks: CursorShellStreamCallbacks,
923
+ signal?: AbortSignal,
924
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
925
+ markNonAbortable?: () => void,
869
926
  ) => Promise<CursorExecHandlerResult<ShellResult>>;
870
- diagnostics?: (args: DiagnosticsArgs) => Promise<CursorExecHandlerResult<DiagnosticsResult>>;
871
- mcp?: (call: CursorMcpCall) => Promise<CursorExecHandlerResult<McpResult>>;
927
+ diagnostics?: (args: DiagnosticsArgs, signal?: AbortSignal) => Promise<CursorExecHandlerResult<DiagnosticsResult>>;
928
+ mcp?: (
929
+ call: CursorMcpCall,
930
+ signal?: AbortSignal,
931
+ /** Marks a started non-abortable operation so the exec terminal waits for settlement. */
932
+ markNonAbortable?: () => void,
933
+ ) => Promise<CursorExecHandlerResult<McpResult>>;
872
934
  piRead?: (call: CursorPiCall<PiReadExecArgs>) => Promise<CursorExecHandlerResult<PiReadExecResult>>;
873
935
  piBash?: (call: CursorPiCall<PiBashExecArgs>) => Promise<CursorExecHandlerResult<PiBashExecResult>>;
874
936
  piEdit?: (call: CursorPiCall<PiEditExecArgs>) => Promise<CursorExecHandlerResult<PiEditExecResult>>;
@@ -1,6 +1,16 @@
1
- export declare const kCursorExecResolved: unique symbol;
2
- export type CursorExecResolvedCarrier = object & {
3
- [kCursorExecResolved]?: true;
1
+ /**
2
+ * Provider-resolved tool-call markers.
3
+ *
4
+ * A provider that executes a tool call itself (Cursor exec-owned calls, or an
5
+ * agent-level ACP provider such as Devin, whose agent runs its own tools) marks
6
+ * the emitted `toolCall` block with this symbol. The agent loop then treats the
7
+ * call as display-only and never dispatches it to a GJC tool. Symbols are used
8
+ * instead of a wire field so the marker exists only in-process and can never
9
+ * round-trip through a transcript or a provider payload.
10
+ */
11
+ export declare const kProviderResolvedToolCall: unique symbol;
12
+ export type ProviderResolvedCarrier = object & {
13
+ [kProviderResolvedToolCall]?: true;
4
14
  };
5
- export declare function isCursorExecResolved(block: CursorExecResolvedCarrier | null | undefined): boolean;
6
- export declare function copyCursorExecResolved(target: CursorExecResolvedCarrier, source: CursorExecResolvedCarrier): void;
15
+ export declare function isProviderResolvedToolCall(block: ProviderResolvedCarrier | null | undefined): boolean;
16
+ export declare function copyProviderResolvedToolCall(target: ProviderResolvedCarrier, source: ProviderResolvedCarrier): void;
@@ -1,11 +1,21 @@
1
- export const kCursorExecResolved = Symbol("provider.block.cursorExecResolved");
1
+ /**
2
+ * Provider-resolved tool-call markers.
3
+ *
4
+ * A provider that executes a tool call itself (Cursor exec-owned calls, or an
5
+ * agent-level ACP provider such as Devin, whose agent runs its own tools) marks
6
+ * the emitted `toolCall` block with this symbol. The agent loop then treats the
7
+ * call as display-only and never dispatches it to a GJC tool. Symbols are used
8
+ * instead of a wire field so the marker exists only in-process and can never
9
+ * round-trip through a transcript or a provider payload.
10
+ */
11
+ export const kProviderResolvedToolCall: unique symbol = Symbol.for("@gajae-code/ai.provider-resolved-tool-call.v1");
2
12
 
3
- export type CursorExecResolvedCarrier = object & { [kCursorExecResolved]?: true };
13
+ export type ProviderResolvedCarrier = object & { [kProviderResolvedToolCall]?: true };
4
14
 
5
- export function isCursorExecResolved(block: CursorExecResolvedCarrier | null | undefined): boolean {
6
- return block?.[kCursorExecResolved] === true;
15
+ export function isProviderResolvedToolCall(block: ProviderResolvedCarrier | null | undefined): boolean {
16
+ return block?.[kProviderResolvedToolCall] === true;
7
17
  }
8
18
 
9
- export function copyCursorExecResolved(target: CursorExecResolvedCarrier, source: CursorExecResolvedCarrier): void {
10
- if (source[kCursorExecResolved] === true) target[kCursorExecResolved] = true;
19
+ export function copyProviderResolvedToolCall(target: ProviderResolvedCarrier, source: ProviderResolvedCarrier): void {
20
+ if (source[kProviderResolvedToolCall] === true) target[kProviderResolvedToolCall] = true;
11
21
  }
@@ -5,6 +5,7 @@ import { getBundledModels } from "../../models";
5
5
  import { CURSOR_CLIENT_VERSION } from "../../providers/cursor/client-version";
6
6
  import { GetUsableModelsRequestSchema, GetUsableModelsResponseSchema } from "../../providers/cursor/gen/agent_pb";
7
7
  import type { Model } from "../../types";
8
+ import { isSafeCatalogModelId } from "./openai-compatible";
8
9
 
9
10
  const CURSOR_DEFAULT_BASE_URL = "https://api2.cursor.sh";
10
11
  const CURSOR_GET_USABLE_MODELS_PATH = "/agent.v1.AgentService/GetUsableModels";
@@ -331,10 +332,10 @@ function normalizeCursorModel(
331
332
  }
332
333
 
333
334
  const details = parsedModel.data;
334
- const id = details.modelId.trim();
335
- if (!id) {
335
+ if (!isSafeCatalogModelId(details.modelId)) {
336
336
  return null;
337
337
  }
338
+ const id = details.modelId.trim();
338
339
 
339
340
  const name = pickModelDisplayName(details, id);
340
341
  const reference = references.get(id);
@@ -1,4 +1,4 @@
1
- export type FallbackTriggerClass = "rate_limit" | "quota" | "auth" | "server" | "unknown" | "other";
1
+ export type FallbackTriggerClass = "rate_limit" | "quota" | "auth" | "credential" | "server" | "unknown" | "other";
2
2
  /**
3
3
  * Refinement of an `auth` trigger.
4
4
  *
@@ -50,6 +50,8 @@ export interface TransportFailureFacts {
50
50
  anthropicErrorType?: string;
51
51
  /** OpenAI's typed `error.code`, preserved separately at the transport boundary. */
52
52
  openaiErrorCode?: string;
53
+ /** Provider-authoritative model rejection that is specific to the active credential. */
54
+ credentialModelUnavailable?: true;
53
55
  headers?: Record<string, string>;
54
56
  /** Safe request-size observation for retry amplification policy. Never contains body content. */
55
57
  requestBytes?: number;
@@ -84,6 +86,7 @@ export interface FallbackTriggerInput {
84
86
  status?: number;
85
87
  providerCode?: string;
86
88
  code?: string;
89
+ credentialModelUnavailable?: boolean;
87
90
  headers?: TransportHeaders;
88
91
  response?: {
89
92
  status?: number;
@@ -1,4 +1,4 @@
1
- export type FallbackTriggerClass = "rate_limit" | "quota" | "auth" | "server" | "unknown" | "other";
1
+ export type FallbackTriggerClass = "rate_limit" | "quota" | "auth" | "credential" | "server" | "unknown" | "other";
2
2
 
3
3
  /**
4
4
  * Refinement of an `auth` trigger.
@@ -55,6 +55,8 @@ export interface TransportFailureFacts {
55
55
  anthropicErrorType?: string;
56
56
  /** OpenAI's typed `error.code`, preserved separately at the transport boundary. */
57
57
  openaiErrorCode?: string;
58
+ /** Provider-authoritative model rejection that is specific to the active credential. */
59
+ credentialModelUnavailable?: true;
58
60
  headers?: Record<string, string>;
59
61
  /** Safe request-size observation for retry amplification policy. Never contains body content. */
60
62
  requestBytes?: number;
@@ -109,6 +111,7 @@ export interface FallbackTriggerInput {
109
111
  status?: number;
110
112
  providerCode?: string;
111
113
  code?: string;
114
+ credentialModelUnavailable?: boolean;
112
115
  headers?: TransportHeaders;
113
116
  response?: { status?: number; headers?: TransportHeaders };
114
117
  error?: { code?: string; type?: string };
@@ -239,6 +242,7 @@ export function transportFailureFacts(
239
242
  const endpointClassValue = propertyOf(value, "endpointClass");
240
243
  const endpointClass =
241
244
  endpointClassValue === "canonical" || endpointClassValue === "custom" ? endpointClassValue : undefined;
245
+ const credentialModelUnavailable = propertyOf(value, "credentialModelUnavailable") === true;
242
246
  if (
243
247
  status === undefined &&
244
248
  headers === undefined &&
@@ -253,6 +257,7 @@ export function transportFailureFacts(
253
257
  // must not materialize facts that would disqualify an unrelated
254
258
  // bare-default retry.
255
259
  providerCode !== SERVER_OVERLOADED_PROVIDER_CODE &&
260
+ !credentialModelUnavailable &&
256
261
  requestBytes === undefined &&
257
262
  firstEventElapsedMs === undefined &&
258
263
  firstEventTimeoutMs === undefined &&
@@ -267,6 +272,7 @@ export function transportFailureFacts(
267
272
  providerCode,
268
273
  anthropicErrorType,
269
274
  openaiErrorCode,
275
+ ...(credentialModelUnavailable ? { credentialModelUnavailable: true as const } : {}),
270
276
  headers,
271
277
  ...(requestBytes === undefined ? {} : { requestBytes }),
272
278
  ...(firstEventElapsedMs === undefined ? {} : { firstEventElapsedMs }),
@@ -379,10 +385,11 @@ export function classifyFallbackTrigger(
379
385
  // classify without a status, so it is matched case-sensitively — the same
380
386
  // exactness the parser and the session admission use.
381
387
  const rawCode = rawCodes[0] ?? rawCodes[1] ?? rawCodes[2];
382
- const triggerClass: FallbackTriggerClass =
383
- code === STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE ||
384
- code === EMPTY_RESPONSE_PROVIDER_CODE ||
385
- (facts.status === undefined && rawCode === SERVER_OVERLOADED_PROVIDER_CODE)
388
+ const triggerClass: FallbackTriggerClass = facts.credentialModelUnavailable
389
+ ? "credential"
390
+ : code === STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE ||
391
+ code === EMPTY_RESPONSE_PROVIDER_CODE ||
392
+ (facts.status === undefined && rawCode === SERVER_OVERLOADED_PROVIDER_CODE)
386
393
  ? "server"
387
394
  : isQuotaCode(code)
388
395
  ? "quota"
package/src/utils.d.ts CHANGED
@@ -7,6 +7,19 @@ export declare function toNumber(value: unknown): number | undefined;
7
7
  export declare function toPositiveNumber(value: unknown, fallback: number): number;
8
8
  export declare function toBoolean(value: unknown): boolean | undefined;
9
9
  export declare function normalizeToolCallId(id: string): string;
10
+ /**
11
+ * Wire-facing tool call id for a canonical ToolCall served to a foreign client.
12
+ *
13
+ * Responses-backed upstreams (Codex, OpenAI Responses) encode the tool call as
14
+ * `${call_id}|${item_id}` in `ToolCall.id` so their own replay can recover the
15
+ * item id. That encoding is gjc-internal: a downstream OpenAI-format client
16
+ * (OpenCodex, another gjc) truncates the compound value at its own 64-char
17
+ * limit and can no longer pair its tool output with the call it echoed back,
18
+ * so the whole chained turn is rejected with "No tool output found". Emit only
19
+ * the `call_id` half on the wire; the item id travels separately as the item
20
+ * `id` where the wire format has one.
21
+ */
22
+ export declare function wireToolCallId(id: string): string;
10
23
  type ResponsesToolItemIdPrefix = "fc" | "ctc";
11
24
  export declare function normalizeResponsesToolCallId(id: string, itemPrefix?: ResponsesToolItemIdPrefix): {
12
25
  callId: string;
package/src/utils.ts CHANGED
@@ -64,6 +64,23 @@ export function normalizeToolCallId(id: string): string {
64
64
  return sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
65
65
  }
66
66
 
67
+ /**
68
+ * Wire-facing tool call id for a canonical ToolCall served to a foreign client.
69
+ *
70
+ * Responses-backed upstreams (Codex, OpenAI Responses) encode the tool call as
71
+ * `${call_id}|${item_id}` in `ToolCall.id` so their own replay can recover the
72
+ * item id. That encoding is gjc-internal: a downstream OpenAI-format client
73
+ * (OpenCodex, another gjc) truncates the compound value at its own 64-char
74
+ * limit and can no longer pair its tool output with the call it echoed back,
75
+ * so the whole chained turn is rejected with "No tool output found". Emit only
76
+ * the `call_id` half on the wire; the item id travels separately as the item
77
+ * `id` where the wire format has one.
78
+ */
79
+ export function wireToolCallId(id: string): string {
80
+ const separator = id.indexOf("|");
81
+ return separator === -1 ? id : id.slice(0, separator);
82
+ }
83
+
67
84
  type ResponsesToolItemIdPrefix = "fc" | "ctc";
68
85
 
69
86
  export function normalizeResponsesToolCallId(
@@ -1,22 +0,0 @@
1
- /**
2
- * Model entitlement facts shared by Codex credential selection and provider
3
- * error presentation.
4
- *
5
- * GPT-5.6 Sol is a Pro-tier ChatGPT Codex model. The usage endpoint is the
6
- * authority for the account tier; this module only names the model policy and
7
- * keeps the provider's deterministic rejection wording in one place.
8
- */
9
- export type OpenAICodexProEntitlement = "entitled" | "denied" | "unknown";
10
- /**
11
- * Classify a ChatGPT `plan_type` for strict Pro-tier Codex models.
12
- *
13
- * The usage endpoint remains authoritative: only exact, documented tier names
14
- * are classified. Known Free/Plus tiers can be rejected locally, while missing
15
- * or unfamiliar values stay unknown and reach the provider instead of being
16
- * guessed from a substring.
17
- */
18
- export declare function classifyOpenAICodexProEntitlement(planType: string | undefined): OpenAICodexProEntitlement;
19
- export declare function requiresOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
20
- export declare function requiresStrictOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
21
- export declare function isOpenAICodexChatGPTEntitlementError(message: string | undefined, code?: string): boolean;
22
- export declare function formatOpenAICodexChatGPTEntitlementError(modelId: string | undefined): string;
@@ -1,22 +0,0 @@
1
- /**
2
- * Model entitlement facts shared by Codex credential selection and provider
3
- * error presentation.
4
- *
5
- * GPT-5.6 Sol is a Pro-tier ChatGPT Codex model. The usage endpoint is the
6
- * authority for the account tier; this module only names the model policy and
7
- * keeps the provider's deterministic rejection wording in one place.
8
- */
9
- export type OpenAICodexProEntitlement = "entitled" | "denied" | "unknown";
10
- /**
11
- * Classify a ChatGPT `plan_type` for strict Pro-tier Codex models.
12
- *
13
- * The usage endpoint remains authoritative: only exact, documented tier names
14
- * are classified. Known Free/Plus tiers can be rejected locally, while missing
15
- * or unfamiliar values stay unknown and reach the provider instead of being
16
- * guessed from a substring.
17
- */
18
- export declare function classifyOpenAICodexProEntitlement(planType: string | undefined): OpenAICodexProEntitlement;
19
- export declare function requiresOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
20
- export declare function requiresStrictOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
21
- export declare function isOpenAICodexChatGPTEntitlementError(message: string | undefined, code?: string): boolean;
22
- export declare function formatOpenAICodexChatGPTEntitlementError(modelId: string | undefined): string;
@@ -1,57 +0,0 @@
1
- /**
2
- * Model entitlement facts shared by Codex credential selection and provider
3
- * error presentation.
4
- *
5
- * GPT-5.6 Sol is a Pro-tier ChatGPT Codex model. The usage endpoint is the
6
- * authority for the account tier; this module only names the model policy and
7
- * keeps the provider's deterministic rejection wording in one place.
8
- */
9
-
10
- const OPENAI_CODEX_PRO_ENTITLED_PLAN_TYPES = new Set(["pro", "business", "enterprise", "team"]);
11
- const OPENAI_CODEX_PRO_DENIED_PLAN_TYPES = new Set(["free", "plus"]);
12
-
13
- export type OpenAICodexProEntitlement = "entitled" | "denied" | "unknown";
14
-
15
- /**
16
- * Classify a ChatGPT `plan_type` for strict Pro-tier Codex models.
17
- *
18
- * The usage endpoint remains authoritative: only exact, documented tier names
19
- * are classified. Known Free/Plus tiers can be rejected locally, while missing
20
- * or unfamiliar values stay unknown and reach the provider instead of being
21
- * guessed from a substring.
22
- */
23
- export function classifyOpenAICodexProEntitlement(planType: string | undefined): OpenAICodexProEntitlement {
24
- const normalized = planType?.trim().toLowerCase();
25
- if (!normalized) return "unknown";
26
- if (OPENAI_CODEX_PRO_ENTITLED_PLAN_TYPES.has(normalized)) return "entitled";
27
- if (OPENAI_CODEX_PRO_DENIED_PLAN_TYPES.has(normalized)) return "denied";
28
- return "unknown";
29
- }
30
-
31
- export function requiresOpenAICodexProModel(provider: string, modelId: string | undefined): boolean {
32
- return (
33
- provider === "openai-codex" &&
34
- typeof modelId === "string" &&
35
- (modelId.toLowerCase().includes("-spark") || modelId.toLowerCase() === "gpt-5.6-sol")
36
- );
37
- }
38
-
39
- export function requiresStrictOpenAICodexProModel(provider: string, modelId: string | undefined): boolean {
40
- return provider === "openai-codex" && modelId?.toLowerCase() === "gpt-5.6-sol";
41
- }
42
-
43
- export function isOpenAICodexChatGPTEntitlementError(message: string | undefined, code?: string): boolean {
44
- return (
45
- /\bnot supported when using codex with a chatgpt account\b/i.test(message ?? "") &&
46
- (code === undefined || code.toLowerCase() === "invalid_request_error")
47
- );
48
- }
49
-
50
- export function formatOpenAICodexChatGPTEntitlementError(modelId: string | undefined): string {
51
- const safeModelId = modelId
52
- ?.replace(/[\x00-\x1f\x7f-\x9f]+/gu, " ")
53
- .trim()
54
- .slice(0, 128);
55
- const model = safeModelId ? ` model "${safeModelId}"` : " model";
56
- return `This ChatGPT Codex account cannot use${model}. Select a model available to this ChatGPT account, such as "gpt-5.5", or use an API-key credential that supports the model.`;
57
- }