@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/CHANGELOG.md CHANGED
@@ -2,14 +2,57 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.17.1] - 2026-09-17
6
+
7
+ ## [0.17.0] - 2026-09-17
8
+
9
+ ### Added
10
+
11
+ - Devin CLI is now a first-class provider (`devin`, api `devin-acp`). Devin publishes no model-inference endpoint, so GJC speaks its documented programmatic surface instead: it spawns `devin acp` and drives the Agent Client Protocol over stdio. Devin owns model selection (discovered from the account's ACP session `model` config option), tool execution, conversation history, and usage; GJC renders Devin's tool calls read-only and always ends the turn with a normal stop so they are never re-executed, forwards cancellation as ACP `session/cancel`, and answers Devin's permission requests from `GJC_DEVIN_PERMISSION_MODE` (`allow` grants `allow_once` and cancels when none is offered, never granting a persistent approval; `deny` rejects; invalid values fail closed). Maintenance and utility calls that need a text model — compaction, handoff, branch summaries, session titles — are refused instead of being spent on a Devin agent turn; `StreamOptions.maintenanceCall` marks them. See `docs/devin-provider.md`.
12
+
13
+ ### Changed
14
+
15
+ - `@gajae-code/ai/utils/block-symbols` now exports the provider-neutral provider-resolved tool-call marker: `kProviderResolvedToolCall`, `ProviderResolvedCarrier`, `isProviderResolvedToolCall`, and `copyProviderResolvedToolCall` replace `kCursorExecResolved`, `CursorExecResolvedCarrier`, `isCursorExecResolved`, and `copyCursorExecResolved`. Cursor exec-owned calls and Devin's ACP tool calls both use it, and it is registered with `Symbol.for` so duplicate module instances cannot disagree about it.
16
+
17
+ ### Fixed
18
+
19
+ - Cancelling an OAuth credential lookup during a shared token refresh no longer backs off the healthy credential or marks its session selector unavailable. Cancellation rejects only that caller; peers can still complete the shared refresh, and genuine refresh failures retain their existing handling.
20
+
21
+ - Usage-limit marking now captures a stable stored row at mark time, including when no explicit row ID is supplied, and re-finds that same row after awaiting usage. Concurrent pointer reassignment or row reordering cannot redirect the mark; a vanished target marks nothing. The same stable-target operation supports OAuth account-specific model-rejection backoff without changing entitlement policy or adding dispatch-bound credential attribution (#5422).
22
+ - The image generation role can select OpenAI's current GPT Image models. `gpt-image-2.5-sunburst` (editing precision) and `gpt-image-2.5-flare` (fast everyday generation) now ship in the bundled catalog under both `openai` and `openai-codex` with the same image-only, zero-cost, 128k-context/16k-max-token shape as `gpt-image-2`, so a provider-qualified `modelRoles.image: openai-codex/gpt-image-2.5-sunburst` selector resolves instead of failing with `No image model configured` (#5478).
23
+ - OpenCode Go's exact `muse-spark-1.3-contributor` model now uses the existing Responses transport with minimal-through-xhigh reasoning, text/image input, and Go pricing. The Go discovery mapper applies the provisional reviewed models.dev Contributor limits over endpoint-reported limits; these are not a published Meta 1.3 specification. The subsequent model-manager merge repairs pre-upgrade ID-only cache placeholders even when fresh, while preserving other already-mapped dynamic limits.
24
+
25
+ - A Responses stream that fails in an HTTP 200 terminal envelope, throws a top-level `error` event, or closes without any terminal event now carries the bounded `upstream_stream_interrupted` classifier instead of a plain error, and `processResponsesStream` reports whether a terminal event was observed. `response.incomplete` is recognized as a terminal event and derives `stopReason: "length"` from the frame type itself rather than from `response.status`, so a relay that drops or rewrites that field cannot turn a length-truncated turn into a plain `stop`/`toolUse` that dispatches a tool call carrying repaired partial arguments, and the interruption classifier cannot fire on a legitimate truncation. The classifier is a safe assistant-message `errorCode`, not a transport-retry fact, so it cannot admit a replay (#5477).
26
+ - Credential-scoped model discovery now peeks the OAuth account selected for that session instead of falling back to unscoped pool ranking. An expired hard-pinned token returns unavailable rather than querying another account's catalog, while AUTO and callers without a scope retain their existing selection behavior.
27
+
28
+ - The Cursor conversation blob store no longer bricks a long session. Its entry ceiling sat below the working set of an ordinary long conversation — a few hundred small blobs — and request construction wrote past that ceiling without being charged for it, so every later server `setBlob` was refused and every tool result that depended on one failed with `Cursor blob store exceeded its bounded capacity` for the rest of the session; neither compaction nor restarting the process recovered it. The store is now bounded by bytes alone, both writers are charged to that budget, and an overflowing write sheds the oldest entries instead of being refused (#5454).
29
+ - Auth-broker failure reasons are scanned in linear time. Two rules accepted an unbounded scheme before the literal `://`, so a long run of scheme characters was re-tried at every prefix: 120 KB of upstream failure text cost roughly two seconds. Upstream reason text is remote-influenced, and `cleanReason` is what makes it safe for less-trusted surfaces.
30
+ - OpenAI Codex credential selection no longer classifies, ranks, filters, or rejects OAuth credentials from ChatGPT `plan_type` labels. Selected models, including GPT-5.6 Sol and Spark variants, now reach the provider under the ordinary credential health, cooldown, quota, and explicit-selection rules; provider-confirmed account-specific model rejection permits one content-free retry on the next unpinned OAuth credential while preserving provider evidence instead of rewriting it as a client-authored account restriction (#5410). This supersedes the earlier Sol-only correction (#5270), including its retained Pro preference and Spark filter.
31
+ - Cursor's first-event deadline now bounds asynchronous payload hooks before any authenticated request is opened, and successful HTTP/2 teardown sends END_STREAM before falling back to bounded cleanup for a peer that leaves its response half open (#4834 review).
32
+ - Cursor conversation state and attachment blobs now reuse only within the same endpoint, credential, model, prompt, tool, and message-prefix authority. Request-local cache updates commit only after a successful terminal, preventing reused caller IDs or failed streams from disclosing prior session state (#4834 review).
33
+ - Cursor Connect watchdog and terminal admission now preserve buffered raw progress, partial output, and usage across bounded held-exec backpressure and transport failures.
34
+ - Cursor Connect streams now own their raw protobuf-progress watchdog, so valid heartbeat, usage, checkpoint, and exec frames keep active turns alive even when no normalized assistant event is emitted. Truly silent transports still time out through Cursor's provider terminal, preserving accumulated partial output and usage instead of replacing them with a zero-usage message (#4831).
35
+ - Coalesced Cursor Connect bursts now apply bounded parser backpressure instead of rejecting the 257th valid frame before queued microtasks can drain; HTTP/2 end waits until every buffered frame is parsed, preserving raw progress and partial usage through the final terminal (#4831 review).
36
+ - Cursor's provider-owned idle timeout uses the established `stream stalled while waiting for the next event` identity, and gRPC trailer, queue, and transport failures all settle behind any in-flight non-abortable exec fence before publishing a terminal (#4834 review).
37
+ - The non-abortable terminal fence now covers every Cursor mutation dispatch, not only `piWrite`: native `writeArgs` dispatches and Pi edit dispatches forward `markNonAbortable` through `resolveExecHandler`, so no started non-abortable filesystem mutation can still be running after the exec terminal is published on caller abort or the local deadline (#4834 review).
38
+ - Cursor arms its first-event watchdog BEFORE the proxy tunnel handshake, so a caller-supplied `streamFirstEventTimeoutMs` shorter than the tunnel's fixed 30-second connect timeout actually bounds proxy setup instead of being ignored until after the handshake completes (#4834 review).
39
+ - The pre-proxy first-event deadline actually interrupts a stalled tunnel handshake: the connect is raced against the same deadline, a tunnel that completes after the deadline won is destroyed instead of leaking, and the authenticated request is never created once the stream already settled (#4834 review).
40
+ - The Cursor bridge withholds the per-exec AbortSignal from non-abortable tools (matching agent-loop.ts) so an `untilAborted(signal, ...)` rejection can no longer make the settlement fence publish the terminal while the mutation still runs; the production Agent run guard forwards the `markNonAbortable` marker for native write dispatches (#4834 review).
41
+ - Cursor Connect exec handlers now receive a per-exec AbortSignal. The caller abort and the local exec deadline both abort it, and the coding-agent bridge threads it into `tool.execute`, so timed-out or caller-cancelled Cursor-local tools can actually stop instead of running to completion after the turn terminalizes. The transport abort fence is also installed before payload setup and rechecked immediately before proxy connect and request creation, closing the race where cancellation won after the check while bearer credentials were still transmitted (#4834 review).
42
+ - A started non-abortable Cursor mutation now keeps stream/run terminal publication behind the mutation's actual settlement. Caller abort and the local exec deadline still determine the eventual terminal reason, but neither can publish while an archive write may still commit, preventing post-terminal filesystem mutation (#4834 review).
43
+ - Cursor `delete` is now part of the non-abortable settlement fence: its dispatch forwards `markNonAbortable` through the Agent run guard and the coding-agent bridge marks before the unlink runs, so a caller abort or deadline can no longer publish the exec terminal while the deletion is still in flight (#4834 review).
44
+ - Cursor usage-context caching now hashes only normalized wire-visible tool definitions instead of complete class-backed tool instances. Session state containing filesystem `bigint` identities can no longer fail requests during preflight serialization, while tool name, description, and schema changes still invalidate cached conversation state.
45
+
46
+ - Claude Code compatibility attribution moved from `2.1.257` to `2.1.273`, and the Gemini CLI spoofed version from `0.58.0` to `0.60.0`. Anthropic gates newer models behind a minimum client version, so a stale `claude-cli/<version>` fingerprint surfaces as an HTTP 400 on a model the account can otherwise reach. Both constants had drifted since the scheduled `spoofed-version-sync` check landed, which is what failed that job.
47
+
5
48
  ## [0.16.7] - 2026-09-13
6
49
 
7
50
  ## [0.16.6] - 2026-09-07
8
51
 
9
52
  ## [0.16.5] - 2026-09-07
10
-
11
53
  - Documented `GJC_OPENAI_CODE_WEBSOCKET_V2` as a switch that enables a websocket v2 path. No code read it under that name, under the legacy `PI_CODEX_WEBSOCKET_V2`, or under the `PI_OPENAI_CODE_WEBSOCKET_V2` the historical entry records; the v2 beta header has been unconditional for websocket transport. The documentation row is removed rather than reintroducing a knob, and the test that claimed to gate on it no longer writes an environment variable nothing reads.
12
54
  - Maintenance reasoning now fails closed for Anthropic models routed through an unverified custom endpoint and for raw reasoning-enabled models without thinking metadata. This prevents unsupported thinking controls and avoids a synchronous missing-metadata crash before provider wire transformation.
55
+ - The auth-gateway OpenAI Responses and Chat Completions encoders now emit only the `call_id` half of a Codex/Responses compound tool-call id (`call_…|fc_…`) on the wire. The compound encoding is gjc-internal replay state; a downstream OpenAI-format client that echoed it back truncated it at its own 64-character limit and every chained tool turn was then rejected with `400 No tool output found for function call`. The item id still travels as the Responses item `id`.
13
56
 
14
57
  ## [0.16.4] - 2026-09-05
15
58
 
@@ -622,6 +622,10 @@ export declare class AuthStorage {
622
622
  setSessionCredentialAuto(provider: string, scopeId: string): void;
623
623
  /** Clear a scope's explicit selector and AUTO mask, restoring normal precedence. */
624
624
  clearSessionCredentialSelector(provider: string, scopeId: string): void;
625
+ /** Preserve a failed hard pin as unavailable instead of allowing AUTO fallback. */
626
+ markSessionCredentialUnavailable(scopeId: string, provider: string, selector: AuthCredentialSelector): void;
627
+ /** Return a failed hard pin retained for this scope, if any. */
628
+ hasSessionCredentialUnavailable(provider: string, scopeId?: string): boolean;
625
629
  /** Whether the effective selection for a scope is explicitly pinned (AUTO masks are not pins). */
626
630
  hasSessionCredentialSelector(provider: string, scopeId?: string): boolean;
627
631
  /** Whether this scope explicitly masks provider pins and uses AUTO ranking. */
@@ -887,15 +891,28 @@ export declare class AuthStorage {
887
891
  checkApiKeyCredential(provider: Provider, apiKey: string, options?: ApiKeyCredentialCheckOptions): Promise<ApiKeyCredentialCheckResult>;
888
892
  checkCredentials(options?: CheckCredentialsOptions): Promise<CredentialHealthResult[]>;
889
893
  /**
890
- * Marks the current session's credential as temporarily blocked due to usage limits.
891
- * Uses usage reports to determine accurate reset time when available.
892
- * Returns true if a credential was blocked, enabling automatic fallback to the next credential.
894
+ * Marks the explicit stored row, or the session row captured at entry, as usage-limited.
895
+ * Re-finds that same row after the usage lookup; a vanished row marks nothing.
896
+ * Returns whether another credential of the same type remains unblocked.
893
897
  */
894
898
  markUsageLimitReached(provider: string, sessionId: string | undefined, options?: {
895
899
  retryAfterMs?: number;
896
900
  baseUrl?: string;
897
901
  signal?: AbortSignal;
898
902
  owner?: object;
903
+ rowId?: number;
904
+ }): Promise<boolean>;
905
+ /**
906
+ * Mark the stored credential whose key matches `apiKey` usage-limited for
907
+ * `retryAfterMs` (or the default backoff). Used when the caller knows
908
+ * exactly which leased credential failed (e.g. the auth-gateway leases a
909
+ * concrete row without a session binding) so bookkeeping cannot land on the
910
+ * wrong credential. The credential is never invalidated or marked suspect:
911
+ * the block is a bounded backoff only. Returns false when no stored
912
+ * credential matches the key.
913
+ */
914
+ markUsageLimitReachedMatching(provider: string, apiKey: string, options?: {
915
+ retryAfterMs?: number;
899
916
  }): Promise<boolean>;
900
917
  /**
901
918
  * Earliest instant at which any currently blocked stored credential for this
@@ -911,7 +928,9 @@ export declare class AuthStorage {
911
928
  * and get a best-effort token. For GitHub Copilot we preserve enterprise
912
929
  * routing metadata so discovery can hit the correct host.
913
930
  */
914
- peekApiKey(provider: string, options?: Pick<AuthApiKeyOptions, "owner">): Promise<string | undefined>;
931
+ peekApiKey(provider: string, options?: Pick<AuthApiKeyOptions, "owner"> & {
932
+ sessionId?: string;
933
+ }): Promise<string | undefined>;
915
934
  /**
916
935
  * Get API key for a provider.
917
936
  * Priority:
@@ -18,4 +18,18 @@ export declare function calculateCost<TApi extends Api>(model: Model<TApi>, usag
18
18
  * Returns false if either model is null or undefined.
19
19
  */
20
20
  export declare function modelsAreEqual<TApi extends Api>(a: Model<TApi> | null | undefined, b: Model<TApi> | null | undefined): boolean;
21
+ /**
22
+ * APIs backed by agent-level providers — currently Devin over ACP. An
23
+ * agent-level provider runs its own agent loop and owns the conversation
24
+ * history, so it refuses GJC maintenance calls (`maintenanceCall` requests such
25
+ * as compaction summaries, handoff generation, and branch summaries) instead of
26
+ * spending billed agent quota on work it cannot answer as a text model.
27
+ */
28
+ export declare const AGENT_LEVEL_PROVIDER_APIS: ReadonlySet<Api>;
29
+ /**
30
+ * Whether a model's provider can serve GJC maintenance calls at all. When this
31
+ * is false for every model a maintenance action can reach, the call can only
32
+ * report a guaranteed refusal, so callers should skip rather than surface it.
33
+ */
34
+ export declare function modelSupportsMaintenanceCalls(model: Model<Api>): boolean;
21
35
  export {};
@@ -1,5 +1,17 @@
1
1
  import type { ModelManagerOptions } from "../model-manager";
2
2
  export declare function openCodexModelManagerOptions(): ModelManagerOptions<"openai-responses">;
3
+ export interface DevinModelManagerConfig {
4
+ cliPath?: string;
5
+ cliArgs?: readonly string[];
6
+ cwd?: string;
7
+ }
8
+ /**
9
+ * Devin models come from the ACP session's own `model` config option: the
10
+ * account and enterprise allowlists are authoritative there, and ACP is the
11
+ * documented programmatic surface (`devin acp`). Discovery fails closed to "no
12
+ * models" when the CLI is missing or unauthenticated.
13
+ */
14
+ export declare function devinModelManagerOptions(config?: DevinModelManagerConfig): ModelManagerOptions<"devin-acp">;
3
15
  export interface OpenAICodexModelManagerConfig {
4
16
  accessToken?: string;
5
17
  accountId?: string;
@@ -107,7 +107,7 @@ export interface CpaToolAliasRestoreFailure {
107
107
  */
108
108
  export declare function parseCpaToolAliasRestoreFailure(error: unknown): CpaToolAliasRestoreFailure | undefined;
109
109
  export declare function isCpaToolAliasRestoreFailure(error: unknown): boolean;
110
- export declare const claudeCodeVersion = "2.1.257";
110
+ export declare const claudeCodeVersion = "2.1.273";
111
111
  export declare const claudeCodeEntrypoint = "sdk-cli";
112
112
  export declare const claudeToolPrefix: string;
113
113
  export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
@@ -1,9 +1,9 @@
1
+ import http2 from "node:http2";
1
2
  import { type JsonValue } from "@bufbuild/protobuf";
2
- import type { AssistantMessage, CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, ToolCall, ToolResultMessage, Usage } from "../types";
3
- import { kCursorExecResolved } from "../utils/block-symbols";
3
+ import type { AssistantMessage, CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, Tool, ToolCall, ToolResultMessage, Usage } from "../types";
4
+ import { kProviderResolvedToolCall } from "../utils/block-symbols";
4
5
  import { CURSOR_CLIENT_VERSION } from "./cursor/client-version";
5
6
  import type { CursorRule, RequestedModel_ModelParameterbytes } from "./cursor/gen/agent_pb";
6
- import { type ConversationStateStructure } from "./cursor/gen/agent_pb";
7
7
  export declare const CURSOR_API_URL = "https://api2.cursor.sh";
8
8
  export { CURSOR_CLIENT_VERSION };
9
9
  /** Drop all cached state + blob bytes for a conversation (F15 bound + session-teardown hook). */
@@ -14,6 +14,24 @@ export interface CursorOptions extends StreamOptions {
14
14
  execHandlers?: CursorExecHandlers;
15
15
  onToolResult?: CursorToolResultHandler;
16
16
  }
17
+ /** Exported for deterministic validation of fragmented Connect progress. */
18
+ export declare function isPlausibleCursorConnectProgressForTest(bufferedLength: number, flags: number, messageLength?: number): boolean;
19
+ /** Exported for deterministic coverage of the Cursor exec-budget derivation. */
20
+ export declare function cursorExecDeadlineMsForTest(idleTimeoutMs: number | undefined): number;
21
+ /** Settlement proof for a started non-abortable Cursor exec. */
22
+ export interface CursorNonAbortableSettlement {
23
+ /** Resolves when the marked mutation settles; never rejects. */
24
+ settled: Promise<void>;
25
+ }
26
+ /** Exported for production-bridge coverage of non-abortable terminal ordering. */
27
+ export declare function runWithCursorExecDeadlineForTest<T>(operation: (signal: AbortSignal, markNonAbortable: () => void) => Promise<T>, signal: AbortSignal | undefined, deadlineMs: number): Promise<T>;
28
+ /** Await request-side END_STREAM under the same bounded teardown contract used by Cursor streams. */
29
+ export declare function endCursorRequestForTest(request: Pick<http2.ClientHttp2Stream, "end">, timeoutMs?: number): Promise<boolean>;
30
+ /** Exported for deterministic coverage of successful writer teardown ordering. */
31
+ export declare function waitForCursorWritesForTest(request: http2.ClientHttp2Stream | null, timeoutMs?: number): Promise<void>;
32
+ export declare function waitForCursorWriteDrainForTest(request: http2.ClientHttp2Stream, timeoutMs?: number): Promise<void>;
33
+ /** Exported for deterministic coverage of the post-fence write race. */
34
+ export declare function writeCursorFrameForTest(request: http2.ClientHttp2Stream, frame: Uint8Array): boolean;
17
35
  /** Build the ordered global USER rules Cursor expects for the current system prompt. */
18
36
  export declare function buildCursorRequestContextRules(systemPrompt: readonly string[] | undefined): CursorRule[];
19
37
  export interface CursorWireModelResolution {
@@ -30,42 +48,34 @@ type ToolCallState = ToolCall & {
30
48
  index: number;
31
49
  partialJson?: string;
32
50
  kind: "mcp" | "todo_write" | "native" | "cursor-exec";
33
- [kCursorExecResolved]?: true;
51
+ [kProviderResolvedToolCall]?: true;
34
52
  };
35
53
  interface UsageState {
36
54
  sawTokenDelta: boolean;
37
- /**
38
- * Latest `ConversationTokenDetails.used_tokens`: the whole conversation's
39
- * token consumption as counted by Cursor, not this turn's output.
40
- */
41
55
  conversationUsedTokens: number;
42
- /** Output tokens already included in the latest checkpoint snapshot. */
43
56
  checkpointOutputTokens: number;
44
- /** Whether the current stream received a checkpoint, including an explicit zero. */
45
57
  hasConversationCheckpoint: boolean;
46
- pendingCheckpoint?: ConversationStateStructure;
47
58
  }
59
+ export declare function storeCursorBlobForTest(blobStore: Map<string, Uint8Array>, blobId: Uint8Array, blobData: Uint8Array, limits: {
60
+ maxBytes: number;
61
+ }): boolean;
48
62
  /** Exported for tests: verifies handler is invoked with correct `this` when passed as bound. */
49
- export declare function resolveExecHandler<TArgs, TResult>(args: TArgs, handler: ((args: TArgs) => Promise<CursorExecHandlerResult<TResult>>) | undefined, onToolResult: CursorToolResultHandler | undefined, buildFromToolResult: (toolResult: ToolResultMessage) => TResult, buildRejected: (reason: string) => TResult, buildError: (error: string) => TResult): Promise<{
63
+ export declare function resolveExecHandler<TArgs, TResult>(args: TArgs, handler: ((args: TArgs, signal?: AbortSignal, markNonAbortable?: () => void) => Promise<CursorExecHandlerResult<TResult>>) | undefined, onToolResult: CursorToolResultHandler | undefined, buildFromToolResult: (toolResult: ToolResultMessage) => TResult, buildRejected: (reason: string) => TResult, buildError: (error: string) => TResult, signal?: AbortSignal, markNonAbortable?: () => void): Promise<{
50
64
  execResult: TResult;
51
65
  toolResult?: ToolResultMessage;
52
66
  }>;
53
67
  /** Exported for deterministic coverage of ordered server-message handling. */
54
- export declare function createCursorMessageQueueForTest(onError?: (error: unknown) => void): {
55
- enqueue(handler: () => void | Promise<void>): Promise<void>;
68
+ export declare function createCursorMessageQueueForTest(onError?: (error: unknown) => void, maxPendingBytes?: number): {
69
+ enqueue(handler: () => void | Promise<void>, byteSize?: number): Promise<void>;
56
70
  drain(): Promise<void>;
71
+ pending(): number;
72
+ pendingBytes(): number;
57
73
  };
58
74
  /** Exported for direct regression coverage of the JSON-safety boundary. */
59
75
  export declare function cursorJsonSafeValueForTest(value: unknown): unknown;
60
76
  export declare function buildNativeToolCallBlock(toolCall: Record<string, unknown>, callId: string, index: number): ToolCallState | null;
61
- /**
62
- * Cursor streams output tokens as deltas and reports whole-conversation
63
- * consumption separately as `ConversationTokenDetails.used_tokens`. Derive
64
- * prompt tokens from the difference so context accounting and compaction see a
65
- * real prompt size instead of zero.
66
- */
77
+ /** Derive prompt usage from Cursor's whole-conversation checkpoint total. */
67
78
  export declare function finalizeCursorUsage(output: AssistantMessage, usageState: UsageState): void;
68
- /** Exposes {@link finalizeCursorUsage} for tests without a live HTTP/2 stream. */
69
79
  export declare function finalizeCursorUsageForTest(usedTokens: number, outputTokens: number, options?: {
70
80
  checkpointOutputTokens?: number;
71
81
  hasConversationCheckpoint?: boolean;
@@ -91,6 +101,8 @@ export declare function finalizeCursorUsageForTest(usedTokens: number, outputTok
91
101
  * an empty `rootPromptMessagesJson` head.
92
102
  */
93
103
  export declare function buildCursorSystemPromptJsons(systemPrompt: readonly string[] | undefined, modelId?: string): string[];
104
+ /** Exported for regression coverage of the tool usage-cache identity boundary. */
105
+ export declare function buildCursorUsageToolsKeyForTest(tools: Tool[]): string;
94
106
  /** Exported for tests: decodes Cursor history blobs built from conversation messages. */
95
107
  export declare function buildCursorHistoryForTest(messages: Message[]): {
96
108
  rootPromptMessagesJson: unknown[];
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Devin CLI provider — an ACP (Agent Client Protocol) client.
3
+ *
4
+ * Devin CLI exposes no raw model-inference endpoint. Its programmatic surface is
5
+ * `devin acp`: an Agent Client Protocol server over stdio that runs the whole
6
+ * agent (https://docs.devin.ai/cli/reference/commands#devin-acp). GJC therefore
7
+ * speaks ACP as the *client* and treats Devin as an agent-level provider:
8
+ *
9
+ * - GJC spawns `devin acp` and speaks ACP over the child's stdio.
10
+ * - One ACP session is reused for the lifetime of a GJC conversation, keyed by
11
+ * `providerSessionId` and stored in `providerSessionState`; `close()` kills
12
+ * the child process at session teardown.
13
+ * - Devin owns conversation history, so only the newest user turn is forwarded.
14
+ * - Devin executes its own tools. `session/update` tool calls are rendered as
15
+ * display-only `toolCall` blocks and every turn terminates with
16
+ * `stopReason: "stop"`, so GJC never re-executes a Devin tool call.
17
+ * - `session/request_permission` is answered from an explicit policy, never
18
+ * silently: see {@link DevinAcpPermissionMode}.
19
+ *
20
+ * Boundary (documented in docs/devin-provider.md): GJC tools, skills, workflows,
21
+ * hooks, and permission prompts for GJC's own tools do not apply inside a Devin
22
+ * turn; GJC maintenance work (compaction, handoff, branch summaries) and utility
23
+ * one-shots are refused rather than forwarded to Devin; and Devin bills its own
24
+ * account/ACU usage.
25
+ */
26
+ import { type ContentBlock, type PermissionOption, type SessionConfigOption, type StopReason } from "@agentclientprotocol/sdk";
27
+ import type { AssistantMessageEventStream as AssistantMessageEventStreamType, Context, Model, StreamOptions } from "../types";
28
+ /** Environment variable overriding the Devin executable GJC spawns. */
29
+ export declare const DEVIN_ACP_CLI_ENV = "GJC_DEVIN_CLI_PATH";
30
+ /** Environment variable selecting how GJC answers Devin permission requests. */
31
+ export declare const DEVIN_ACP_PERMISSION_MODE_ENV = "GJC_DEVIN_PERMISSION_MODE";
32
+ /** Executable that serves `devin acp` when nothing overrides it. */
33
+ export declare const DEVIN_ACP_DEFAULT_CLI = "devin";
34
+ /**
35
+ * Placeholder base URL. The ACP transport never issues an HTTP request, but a
36
+ * model record must carry a non-empty base URL through the model registry.
37
+ */
38
+ export declare const DEVIN_ACP_BASE_URL = "acp://devin-cli";
39
+ /**
40
+ * Conservative catalog defaults. ACP exposes no per-model token metadata, and
41
+ * GJC never sends conversation history to an ACP agent, so these values are
42
+ * display-only for this provider rather than a transport budget.
43
+ */
44
+ export declare const DEVIN_ACP_CONTEXT_WINDOW = 200000;
45
+ export declare const DEVIN_ACP_MAX_TOKENS = 64000;
46
+ /**
47
+ * How GJC answers Devin's `session/request_permission` prompts.
48
+ *
49
+ * - `"allow"` (default) selects `allow_once`. A request that offers no
50
+ * `allow_once` is cancelled: GJC never grants a persistent approval on its own.
51
+ * - `"deny"` selects `reject_once` before `reject_always`.
52
+ *
53
+ * Neither mode escalates persistently on its own, and an unrecognized
54
+ * `GJC_DEVIN_PERMISSION_MODE` value fails closed to `"deny"`.
55
+ */
56
+ export type DevinAcpPermissionMode = "allow" | "deny";
57
+ /** A permission request Devin raised for one of its own tool calls. */
58
+ export interface DevinAcpPermissionRequest {
59
+ sessionId: string;
60
+ toolCallId: string;
61
+ title: string;
62
+ kind?: string;
63
+ rawInput?: unknown;
64
+ options: ReadonlyArray<{
65
+ optionId: string;
66
+ name: string;
67
+ kind: string;
68
+ }>;
69
+ }
70
+ /** Selected option id, or `cancelled` to answer with ACP `outcome: cancelled`. */
71
+ export type DevinAcpPermissionDecision = {
72
+ optionId: string;
73
+ } | {
74
+ cancelled: true;
75
+ };
76
+ /** Explicit decision callback; replaces the built-in permission mode policy. */
77
+ export type DevinAcpPermissionHandler = (request: DevinAcpPermissionRequest) => Promise<DevinAcpPermissionDecision> | DevinAcpPermissionDecision;
78
+ /** Provider configuration threaded through `StreamOptions.devinAcp`. */
79
+ export interface DevinAcpConfig {
80
+ /** Executable serving `devin acp`. Defaults to {@link DEVIN_ACP_CLI_ENV} or `devin`. */
81
+ cliPath?: string;
82
+ /** Extra argv inserted before the `acp` verb. */
83
+ cliArgs?: readonly string[];
84
+ /** Working directory for the agent process. Defaults to `process.cwd()`. */
85
+ cwd?: string;
86
+ /**
87
+ * Overrides the permission policy for this request. Any value other than
88
+ * `"allow"` fails closed to `"deny"`, including out-of-type values from
89
+ * untyped callers.
90
+ */
91
+ permissionMode?: DevinAcpPermissionMode;
92
+ /** Explicit permission decisions. When absent, the configured mode decides. */
93
+ permissionHandler?: DevinAcpPermissionHandler;
94
+ }
95
+ /**
96
+ * Devin ACP stream options. Every provider-specific field lives on
97
+ * `StreamOptions.devinAcp`; the alias exists so `ApiOptionsMap` can name this
98
+ * API's option type like every other API does.
99
+ */
100
+ export type DevinAcpOptions = StreamOptions;
101
+ /** Map an ACP tool kind to the display tool name GJC renders. */
102
+ export declare function devinAcpDisplayToolName(kind: string | null | undefined, name?: string | null): string;
103
+ /**
104
+ * Copy a tool-call `rawInput` payload into a transcript-safe `arguments` record.
105
+ *
106
+ * ACP payloads arrive through JSON-RPC, so they are already JSON-shaped; this
107
+ * only wraps non-objects and refuses to stage an unbounded payload.
108
+ */
109
+ export declare function devinAcpToolArguments(rawInput: unknown): Record<string, unknown>;
110
+ /** Map an ACP prompt stop reason onto GJC's assistant stop reason. */
111
+ export declare function devinAcpStopReason(stopReason: StopReason): "stop" | "length" | "aborted";
112
+ /** Flatten ACP select option groups into `{ id, name }` model entries. */
113
+ export declare function devinAcpSelectOptions(options: Extract<SessionConfigOption, {
114
+ type: "select";
115
+ }>["options"]): Array<{
116
+ id: string;
117
+ name: string;
118
+ }>;
119
+ /**
120
+ * Select the option matching the configured policy.
121
+ *
122
+ * `allow` grants a single action and never a persistent one: when the agent
123
+ * offers no `allow_once`, the request is cancelled rather than escalated to
124
+ * `allow_always`. `deny` may fall back to `reject_always`, because a persistent
125
+ * refusal only reduces what the agent may do.
126
+ */
127
+ export declare function devinAcpSelectPermissionOption(options: ReadonlyArray<PermissionOption>, mode: DevinAcpPermissionMode): {
128
+ optionId: string;
129
+ } | null;
130
+ /** Resolve the permission policy from an explicit config value or the environment. */
131
+ export declare function devinAcpResolvePermissionMode(configured: DevinAcpPermissionMode | undefined, env?: Record<string, string | undefined>): DevinAcpPermissionMode;
132
+ /** Build the ACP prompt content blocks for the newest user turn. */
133
+ export declare function devinAcpPromptBlocks(context: Context, supportsImages: boolean): ContentBlock[];
134
+ /**
135
+ * Stable identity for the cached ACP child.
136
+ *
137
+ * `cwd` is part of the identity on purpose: `/move` changes `process.cwd()`, and a
138
+ * cached child keeps the directory it was spawned in, so a cwd change must miss the
139
+ * cache and respawn instead of running Devin's tools in the abandoned tree.
140
+ */
141
+ export declare function devinAcpBridgeIdentity(conversationId: string | undefined, cwd: string, argv: readonly string[]): string;
142
+ export declare const streamDevinAcp: (model: Model<"devin-acp">, context: Context, options?: DevinAcpOptions) => AssistantMessageEventStreamType;
143
+ /**
144
+ * Discover the account's Devin models over ACP.
145
+ *
146
+ * `devin models list --format json` is deliberately not parsed: its schema is
147
+ * undocumented, while the session's own `model` config option is the
148
+ * authoritative ACP surface for the authenticated account and enterprise
149
+ * allowlists. Returns `null` when the CLI is missing, unauthenticated, or the
150
+ * agent advertises no model selector — which the model registry treats as
151
+ * "no dynamic models".
152
+ */
153
+ export declare function fetchDevinAcpModels(config?: {
154
+ cliPath?: string;
155
+ cliArgs?: readonly string[];
156
+ cwd?: string;
157
+ }): Promise<Model<"devin-acp">[] | null>;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export declare const GEMINI_CLI_VERSION_ENV = "GJC_AI_GEMINI_CLI_VERSION";
7
7
  export declare const LEGACY_GEMINI_CLI_VERSION_ENV = "PI_AI_GEMINI_CLI_VERSION";
8
- export declare const DEFAULT_GEMINI_CLI_VERSION = "0.58.0";
8
+ export declare const DEFAULT_GEMINI_CLI_VERSION = "0.60.0";
9
9
  export declare function getGeminiCliUserAgent(modelId?: string): string;
10
10
  export declare const getGeminiCliHeaders: (modelId?: string) => {
11
11
  "User-Agent": string;
@@ -87,6 +87,8 @@ export interface MockResponse {
87
87
  providerPayload?: AssistantMessage["providerPayload"];
88
88
  /** Optional typed provider failure metadata for retry/fallback tests. */
89
89
  transportFailure?: AssistantMessage["transportFailure"];
90
+ /** Bounded, redaction-safe failure classifier copied onto the terminal error message. */
91
+ errorCode?: string;
90
92
  /** If set, the stream emits a terminal error event instead of completing. */
91
93
  throw?: string | Error;
92
94
  /** Delay before any event is emitted. Honors the call's AbortSignal. */
@@ -44,7 +44,27 @@ export interface ProcessResponsesStreamOptions {
44
44
  onFirstToken?: () => void;
45
45
  onOutputItemDone?: (item: ResponseOutputItem) => void;
46
46
  }
47
- export declare function processResponsesStream<TApi extends Api>(openaiStream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>, output: AssistantMessage, stream: AssistantMessageEventStream, model: Model<TApi>, options?: ProcessResponsesStreamOptions): Promise<void>;
47
+ export declare function processResponsesStream<TApi extends Api>(openaiStream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>, output: AssistantMessage, stream: AssistantMessageEventStream, model: Model<TApi>, options?: ProcessResponsesStreamOptions): Promise<boolean>;
48
+ /**
49
+ * Bounded classifier for a Responses stream that ended in failure before a
50
+ * successful terminal event: a `response.failed` envelope, a `response.completed`
51
+ * with `failed` status, a top-level `error` event, or an unexpected EOF (the
52
+ * stream returning without any terminal event). It is deliberately NOT a
53
+ * transport-retry fact: `transportFailureFacts` does not admit it, so preserving
54
+ * this diagnostic can never authorize a replay.
55
+ */
56
+ export declare const RESPONSES_STREAM_FAILURE_CODE = "upstream_stream_interrupted";
57
+ /**
58
+ * Typed error for an SSE stream that ended (EOF) before any terminal event, so
59
+ * the caller sees a bounded classifier instead of a content-free success.
60
+ */
61
+ export declare function unexpectedResponsesStreamEndError(): Error;
62
+ /**
63
+ * Read the bounded stream-failure classifier back off a thrown provider error.
64
+ * Only this module's own classifier is returned, so a foreign error's arbitrary
65
+ * `code` can never be forwarded onto the assistant message.
66
+ */
67
+ export declare function responsesStreamFailureCode(error: unknown): string | undefined;
48
68
  /**
49
69
  * Mark tool-call blocks left incomplete by a length-truncated response so the
50
70
  * agent loop rejects them instead of executing a best-effort partial parse.
@@ -53,6 +53,7 @@ export declare const streamOpenAICodexResponses: (model: Model<"openai-codex-res
53
53
  export declare const streamOpenAICompletions: (model: Model<"openai-completions">, context: Context, options: OptionsForApi<"openai-completions">, onStreamCreated?: () => void) => EventStreamImpl;
54
54
  export declare const streamOpenAIResponses: (model: Model<"openai-responses">, context: Context, options: OptionsForApi<"openai-responses">, onStreamCreated?: () => void) => EventStreamImpl;
55
55
  export declare const streamCursor: (model: Model<"cursor-agent">, context: Context, options: OptionsForApi<"cursor-agent">, onStreamCreated?: () => void) => EventStreamImpl;
56
+ export declare const streamDevinAcp: (model: Model<"devin-acp">, context: Context, options: import("..").StreamOptions, onStreamCreated?: () => void) => EventStreamImpl;
56
57
  export declare const streamOllama: (model: Model<"ollama-chat">, context: Context, options: OptionsForApi<"ollama-chat">, onStreamCreated?: () => void) => EventStreamImpl;
57
58
  export declare const streamBedrock: (model: Model<"bedrock-converse-stream">, context: Context, options: OptionsForApi<"bedrock-converse-stream">, onStreamCreated?: () => void) => EventStreamImpl;
58
59
  export declare const streamKiroCodeWhisperer: (model: Model<"kiro-codewhisperer-stream">, context: Context, options: OptionsForApi<"kiro-codewhisperer-stream">, onStreamCreated?: () => void) => EventStreamImpl;
@@ -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>>;