@gajae-code/ai 0.14.2 → 0.15.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 +26 -0
- package/dist/types/auth-storage.d.ts +2 -2
- package/dist/types/model-cache.d.ts +2 -0
- package/dist/types/provider-models/special.d.ts +3 -1
- package/dist/types/providers/anthropic.d.ts +1 -0
- package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
- package/dist/types/providers/cursor/gen/agent_pb.d.ts +3854 -107
- package/dist/types/providers/cursor-pi-args.d.ts +119 -0
- package/dist/types/providers/cursor.d.ts +8 -1
- package/dist/types/providers/openai-codex-responses.d.ts +2 -0
- package/dist/types/providers/openai-responses-shared.d.ts +1 -1
- package/dist/types/types.d.ts +41 -1
- package/dist/types/utils/block-symbols.d.ts +6 -0
- package/dist/types/utils/discovery/openai-compatible.d.ts +2 -0
- package/dist/types/utils/idle-iterator.d.ts +5 -2
- package/dist/types/utils/oauth/kimi.d.ts +3 -9
- package/dist/types/utils/oauth/openrouter.d.ts +1 -0
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/package.json +4 -4
- package/src/auth-broker/remote-store.ts +13 -2
- package/src/auth-storage.ts +10 -11
- package/src/model-cache.ts +78 -0
- package/src/model-manager.ts +194 -25
- package/src/provider-models/special.ts +67 -4
- package/src/providers/anthropic.ts +115 -40
- package/src/providers/aws-credential-config.ts +2 -3
- package/src/providers/aws-credentials.ts +2 -3
- package/src/providers/azure-openai-responses.ts +18 -2
- package/src/providers/cursor/exec-modern.ts +497 -0
- package/src/providers/cursor/gen/agent_pb.ts +4687 -181
- package/src/providers/cursor/proto/agent.proto +1007 -0
- package/src/providers/cursor-pi-args.ts +187 -0
- package/src/providers/cursor.ts +382 -47
- package/src/providers/google-auth.ts +2 -3
- package/src/providers/openai-codex-responses.ts +358 -73
- package/src/providers/openai-completions.ts +2 -2
- package/src/providers/openai-responses-shared.ts +55 -6
- package/src/providers/openai-responses.ts +27 -4
- package/src/stream.ts +8 -3
- package/src/types.ts +55 -0
- package/src/utils/block-symbols.ts +11 -0
- package/src/utils/discovery/openai-compatible.ts +21 -6
- package/src/utils/idle-iterator.ts +22 -4
- package/src/utils/oauth/index.ts +6 -0
- package/src/utils/oauth/kimi.ts +14 -8
- package/src/utils/oauth/kiro.ts +2 -2
- package/src/utils/oauth/openrouter.ts +16 -0
- package/src/utils/oauth/types.ts +1 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate a Pi frame's args into the local tool kwargs that run it.
|
|
3
|
+
*
|
|
4
|
+
* Shared deliberately by three consumers: the provider synthesizes a display
|
|
5
|
+
* block from these, the coding-agent bridge executes with them, and the legacy
|
|
6
|
+
* pi shim performs the identical translation for the old wire. Separate
|
|
7
|
+
* hand-rolled copies drift, and the drift is invisible — the transcript shows
|
|
8
|
+
* one operation while a different one runs.
|
|
9
|
+
*
|
|
10
|
+
* Kept apart from `cursor/exec-modern.ts` on purpose: these are pure
|
|
11
|
+
* string/path functions with no protobuf coupling, while that module pulls in
|
|
12
|
+
* `@bufbuild/protobuf` and the generated `agent_pb` graph. The legacy shim is
|
|
13
|
+
* compiled into the bundled virtual module registry, so importing it from a
|
|
14
|
+
* nested path would drag the whole exec implementation in with it — and
|
|
15
|
+
* `./providers/*` is a single-segment wildcard export that cannot serve a
|
|
16
|
+
* nested specifier under bunfs (issue #3442).
|
|
17
|
+
*
|
|
18
|
+
* Every `optional int32` here is presence-sensitive: `0` is a supplied value,
|
|
19
|
+
* not "unset", so it must never be folded into a default.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* A `pi_read` range composed onto the path as `read`'s inline `:raw:N+K`
|
|
23
|
+
* selector.
|
|
24
|
+
*
|
|
25
|
+
* `read` exposes no range kwargs, so an uncomposed range reads the whole file.
|
|
26
|
+
* `offset` is a 1-indexed start clamped like the reference's
|
|
27
|
+
* `Math.max(0, offset - 1)` over 0-indexed lines; `limit` is a line count.
|
|
28
|
+
* `null` marks a present `limit: 0` — zero lines, which no selector expresses
|
|
29
|
+
* and which must not degrade into a whole-file read.
|
|
30
|
+
*
|
|
31
|
+
* The range is `raw` because a plain `:N+K` deliberately pads with one leading
|
|
32
|
+
* and three trailing context lines: helpful for a human reading a snippet,
|
|
33
|
+
* wrong for a caller that asked for exactly `limit` lines from `offset`. The
|
|
34
|
+
* wire result is an opaque `output` string, so the hashline and line-number
|
|
35
|
+
* gutter that `raw` also drops carry nothing the frame's contract needs.
|
|
36
|
+
* A range-free read keeps the ordinary form — whole-file reads want them.
|
|
37
|
+
*/
|
|
38
|
+
export declare function piReadPath(readPath: string, offset?: number, limit?: number): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Whether a read path ends in an OMP line selector, including compound `raw`
|
|
41
|
+
* forms. Cursor uses this only to describe the operation already executed by
|
|
42
|
+
* the coding-agent read tool; the selector remains embedded in the path.
|
|
43
|
+
*/
|
|
44
|
+
export declare function piReadPathHasRange(readPath: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The same range as {@link piReadPath}, rendered for a transcript block rather
|
|
47
|
+
* than for execution.
|
|
48
|
+
*
|
|
49
|
+
* Differs only at `limit: 0`, where `piReadPath` returns `null` because no
|
|
50
|
+
* selector reads zero lines and the frame is answered with empty output
|
|
51
|
+
* directly. The block still has to say so: falling back to the bare path there
|
|
52
|
+
* would record a whole-file read whose result is empty, which is the widest
|
|
53
|
+
* possible gap between what a rebuilt transcript shows and what happened.
|
|
54
|
+
* `+0` is never executed — it exists to be read.
|
|
55
|
+
*/
|
|
56
|
+
export declare function piReadDisplayPath(readPath: string, offset?: number, limit?: number): string;
|
|
57
|
+
/**
|
|
58
|
+
* A legacy `grep` frame's pagination `offset` as the local tool's file `skip`.
|
|
59
|
+
*
|
|
60
|
+
* `grep` paginates by file and reports "use skip=N for the next page" in that
|
|
61
|
+
* same unit, so the offset maps across directly. A present `0` means "start at
|
|
62
|
+
* the beginning", which is the unskipped search rather than a skip of zero.
|
|
63
|
+
*
|
|
64
|
+
* Shared because both the executing bridge and the provider's transcript
|
|
65
|
+
* synthesis need it: a block showing an unskipped search beside a result from
|
|
66
|
+
* a later file window misreports what was searched.
|
|
67
|
+
*/
|
|
68
|
+
export declare function piGrepSkip(offset?: number): number | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Join a Pi frame's optional `path` with the `glob`/`pattern` it scopes.
|
|
71
|
+
*
|
|
72
|
+
* The local `grep`/`glob` tools take one combined path spec. An absolute
|
|
73
|
+
* pattern ignores the path, and an absent or `.` path leaves the pattern
|
|
74
|
+
* standing alone rather than building a `./`- or `//`-prefixed spec.
|
|
75
|
+
*
|
|
76
|
+
* Uses `node:path` rather than string surgery so Windows absolutes (`C:\…`,
|
|
77
|
+
* UNC) are recognised and separators stay normalized.
|
|
78
|
+
*/
|
|
79
|
+
export declare function piJoinPath(basePath: string | undefined, pattern: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* The path a `pi_ls` frame lists.
|
|
82
|
+
*
|
|
83
|
+
* The frame's `limit` is deliberately NOT mapped. It caps directory *entries*
|
|
84
|
+
* (the reference does a flat `readdir` and slices the entry array), while the
|
|
85
|
+
* local `read` tool renders a depth-2 tree with per-directory caps and elision
|
|
86
|
+
* summaries and applies a selector as a *rendered line* slice. Nested rows,
|
|
87
|
+
* headers and "N more" lines all count toward that slice, so `:1+K` would cap
|
|
88
|
+
* a different unit while looking honored — worse than leaving it unset, which
|
|
89
|
+
* at least reports the local listing's own truncation faithfully.
|
|
90
|
+
*/
|
|
91
|
+
export declare function piLsPath(basePath: string | undefined): string;
|
|
92
|
+
/** Escape a literal string so the regex-only local `grep` tool matches it verbatim. */
|
|
93
|
+
export declare function piEscapeRegexLiteral(value: string): string;
|
|
94
|
+
/** Clamp a present `optional int32` result cap the way the reference does; `undefined` stays unset. */
|
|
95
|
+
export declare function piLimit(limit: number | undefined): number | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* A `pi_bash` frame's timeout as the local `bash` tool's kwarg.
|
|
98
|
+
*
|
|
99
|
+
* Presence-sensitive like every other `optional int32` here, and unusually
|
|
100
|
+
* load-bearing: `bash` documents `timeout: 0` as "disables the command
|
|
101
|
+
* deadline", so folding a supplied `0` into `undefined` applies the 300s
|
|
102
|
+
* default and kills exactly the long-running command that asked not to be.
|
|
103
|
+
* Negative values have no local meaning and fall back to the default.
|
|
104
|
+
*/
|
|
105
|
+
export declare function piTimeout(timeout: number | undefined): number | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Drop keys whose value is `undefined` so optional local-tool kwargs stay
|
|
108
|
+
* absent rather than present-as-undefined.
|
|
109
|
+
*
|
|
110
|
+
* The Cursor exec bridge historically wrote forms like
|
|
111
|
+
* `cwd: workingDirectory || undefined` and
|
|
112
|
+
* `case: caseInsensitive === true ? false : undefined`. ArkType rejects a
|
|
113
|
+
* present `undefined` on an optional field (`was undefined`) even though
|
|
114
|
+
* omitting the key is valid — which flooded Cursor sessions with bash/grep
|
|
115
|
+
* validation errors for otherwise fine frames.
|
|
116
|
+
*/
|
|
117
|
+
export declare function omitUndefinedArgs<T extends Record<string, unknown>>(args: T): {
|
|
118
|
+
[K in keyof T]?: Exclude<T[K], undefined>;
|
|
119
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type JsonValue } from "@bufbuild/protobuf";
|
|
2
2
|
import type { CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, Message, StreamFunction, StreamOptions, ToolCall, ToolResultMessage } from "../types";
|
|
3
|
+
import { kCursorExecResolved } from "../utils/block-symbols";
|
|
3
4
|
import { CURSOR_CLIENT_VERSION } from "./cursor/client-version";
|
|
4
5
|
export declare const CURSOR_API_URL = "https://api2.cursor.sh";
|
|
5
6
|
export { CURSOR_CLIENT_VERSION };
|
|
@@ -15,13 +16,19 @@ export declare const streamCursor: StreamFunction<"cursor-agent">;
|
|
|
15
16
|
type ToolCallState = ToolCall & {
|
|
16
17
|
index: number;
|
|
17
18
|
partialJson?: string;
|
|
18
|
-
kind: "mcp" | "todo_write" | "native";
|
|
19
|
+
kind: "mcp" | "todo_write" | "native" | "cursor-exec";
|
|
20
|
+
[kCursorExecResolved]?: true;
|
|
19
21
|
};
|
|
20
22
|
/** Exported for tests: verifies handler is invoked with correct `this` when passed as bound. */
|
|
21
23
|
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<{
|
|
22
24
|
execResult: TResult;
|
|
23
25
|
toolResult?: ToolResultMessage;
|
|
24
26
|
}>;
|
|
27
|
+
/** Exported for deterministic coverage of ordered server-message handling. */
|
|
28
|
+
export declare function createCursorMessageQueueForTest(onError?: (error: unknown) => void): {
|
|
29
|
+
enqueue(handler: () => void | Promise<void>): Promise<void>;
|
|
30
|
+
drain(): Promise<void>;
|
|
31
|
+
};
|
|
25
32
|
/** Exported for direct regression coverage of the JSON-safety boundary. */
|
|
26
33
|
export declare function cursorJsonSafeValueForTest(value: unknown): unknown;
|
|
27
34
|
export declare function buildNativeToolCallBlock(toolCall: Record<string, unknown>, callId: string, index: number): ToolCallState | null;
|
|
@@ -21,6 +21,8 @@ export interface OpenAICodexWebSocketDebugStats {
|
|
|
21
21
|
}
|
|
22
22
|
/** @internal Exported for tests. */
|
|
23
23
|
export declare function normalizeCodexToolChoice(choice: ToolChoice | undefined, tools?: Tool[], model?: Model<"openai-codex-responses">): string | Record<string, unknown> | undefined;
|
|
24
|
+
/** @internal Exported for tests. */
|
|
25
|
+
export declare function formatCodexUserAgent(platform: string, release: string, arch: string): string;
|
|
24
26
|
export declare const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses">;
|
|
25
27
|
export declare function prewarmOpenAICodexResponses(model: Model<"openai-codex-responses">, options?: Pick<OpenAICodexResponsesOptions, "apiKey" | "headers" | "sessionId" | "signal" | "preferWebsockets" | "providerSessionState">): Promise<void>;
|
|
26
28
|
export interface OpenAICodexTransportDetails {
|
|
@@ -39,7 +39,7 @@ export declare function collectCustomCallIds(messages: ResponseInput): Set<strin
|
|
|
39
39
|
export declare function repairOrphanResponsesToolOutputs(input: ResponseInput): ResponseInput;
|
|
40
40
|
export declare function convertResponsesInputContent(content: string | Array<TextContent | ImageContent>, supportsImages: boolean): ResponseInputContent[] | undefined;
|
|
41
41
|
export declare function convertResponsesAssistantMessage<TApi extends Api>(assistantMsg: AssistantMessage, model: Model<TApi>, msgIndex: number, knownCallIds: Set<string>, includeThinkingSignatures?: boolean, customCallIds?: Set<string>): ResponseInput;
|
|
42
|
-
export declare function appendResponsesToolResultMessages<TApi extends Api>(messages: ResponseInput,
|
|
42
|
+
export declare function appendResponsesToolResultMessages<TApi extends Api>(messages: ResponseInput, toolResults: readonly ToolResultMessage[], model: Model<TApi>, strictResponsesPairing: boolean, knownCallIds: ReadonlySet<string>, customCallIds?: ReadonlySet<string>): void;
|
|
43
43
|
export interface ProcessResponsesStreamOptions {
|
|
44
44
|
onFirstToken?: () => void;
|
|
45
45
|
onOutputItemDone?: (item: ResponseOutputItem) => void;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { BedrockOptions } from "./providers/amazon-bedrock";
|
|
|
3
3
|
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
|
-
import type { DeleteArgs, DeleteResult, DiagnosticsArgs, DiagnosticsResult, GrepArgs, GrepResult, LsArgs, LsResult, McpResult, ReadArgs, ReadResult, ShellArgs, ShellResult, WriteArgs, WriteResult } from "./providers/cursor/gen/agent_pb";
|
|
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
7
|
import type { GoogleOptions } from "./providers/google";
|
|
8
8
|
import type { GoogleGeminiCliOptions } from "./providers/google-gemini-cli";
|
|
9
9
|
import type { GoogleVertexOptions } from "./providers/google-vertex";
|
|
@@ -461,6 +461,26 @@ export interface Usage {
|
|
|
461
461
|
}
|
|
462
462
|
export type StopReason = "stop" | "length" | "toolUse" | "error" | "aborted";
|
|
463
463
|
export type AssistantErrorKind = "provider_safety_stop" | "local_snapshot_failure" | "local_buffer_overflow";
|
|
464
|
+
/**
|
|
465
|
+
* Structured, shape-only staging-buffer overflow diagnostic carried on the
|
|
466
|
+
* terminal `AssistantMessage`. Attached only by the agent runtime from its own
|
|
467
|
+
* identity-checked overflow error; every field is a closed vocabulary literal
|
|
468
|
+
* or a locally synthesized number.
|
|
469
|
+
*/
|
|
470
|
+
export interface AssistantBufferOverflowDiagnostic {
|
|
471
|
+
/** Rejecting stage from the closed managed-local-failure vocabulary. */
|
|
472
|
+
stage: string;
|
|
473
|
+
/** Which provisional cap tripped. */
|
|
474
|
+
exceeded: "events" | "bytes" | "both";
|
|
475
|
+
/** Events retained in the batch at rejection (post-compaction). */
|
|
476
|
+
stagedEventCount: number;
|
|
477
|
+
/** Bytes retained in the batch at rejection (post-compaction). */
|
|
478
|
+
stagedBytes: number;
|
|
479
|
+
/** Serialized size of the event that was rejected. */
|
|
480
|
+
incomingEventBytes: number;
|
|
481
|
+
maxStagedEvents: number;
|
|
482
|
+
maxStagedBytes: number;
|
|
483
|
+
}
|
|
464
484
|
export interface OpenAIResponsesHistoryPayload {
|
|
465
485
|
type: "openaiResponsesHistory";
|
|
466
486
|
provider?: string;
|
|
@@ -499,6 +519,15 @@ export interface AssistantMessage {
|
|
|
499
519
|
stopReason: StopReason;
|
|
500
520
|
errorMessage?: string;
|
|
501
521
|
errorKind?: AssistantErrorKind;
|
|
522
|
+
/**
|
|
523
|
+
* Structured, shape-only diagnostic for a terminal local staging-buffer
|
|
524
|
+
* overflow (`errorKind: "local_buffer_overflow"`). Attached only by the
|
|
525
|
+
* agent runtime from its own identity-checked overflow error, so a
|
|
526
|
+
* foreign, self-labeled error cannot populate it. Every field is a closed
|
|
527
|
+
* vocabulary literal or a locally synthesized number — parent surfaces
|
|
528
|
+
* render this instead of trusting the free-form `errorMessage`.
|
|
529
|
+
*/
|
|
530
|
+
bufferOverflow?: AssistantBufferOverflowDiagnostic;
|
|
502
531
|
/** HTTP status surfaced by the provider when the request failed. Populated by every provider's catch block alongside `errorMessage` so consumers (auth retry, telemetry, UI) can branch without regex-scraping the message. */
|
|
503
532
|
errorStatus?: number;
|
|
504
533
|
/** Typed upstream failure facts retained for retry classification without parsing errorMessage. */
|
|
@@ -548,6 +577,10 @@ export interface CursorShellStreamCallbacks {
|
|
|
548
577
|
onStdout(data: string): void;
|
|
549
578
|
onStderr(data: string): void;
|
|
550
579
|
}
|
|
580
|
+
export interface CursorPiCall<TArgs> {
|
|
581
|
+
args: TArgs;
|
|
582
|
+
toolCallId: string;
|
|
583
|
+
}
|
|
551
584
|
export interface CursorExecHandlers {
|
|
552
585
|
read?: (args: ReadArgs) => Promise<CursorExecHandlerResult<ReadResult>>;
|
|
553
586
|
ls?: (args: LsArgs) => Promise<CursorExecHandlerResult<LsResult>>;
|
|
@@ -558,6 +591,13 @@ export interface CursorExecHandlers {
|
|
|
558
591
|
shellStream?: (args: ShellArgs, callbacks: CursorShellStreamCallbacks) => Promise<CursorExecHandlerResult<ShellResult>>;
|
|
559
592
|
diagnostics?: (args: DiagnosticsArgs) => Promise<CursorExecHandlerResult<DiagnosticsResult>>;
|
|
560
593
|
mcp?: (call: CursorMcpCall) => Promise<CursorExecHandlerResult<McpResult>>;
|
|
594
|
+
piRead?: (call: CursorPiCall<PiReadExecArgs>) => Promise<CursorExecHandlerResult<PiReadExecResult>>;
|
|
595
|
+
piBash?: (call: CursorPiCall<PiBashExecArgs>) => Promise<CursorExecHandlerResult<PiBashExecResult>>;
|
|
596
|
+
piEdit?: (call: CursorPiCall<PiEditExecArgs>) => Promise<CursorExecHandlerResult<PiEditExecResult>>;
|
|
597
|
+
piWrite?: (call: CursorPiCall<PiWriteExecArgs>) => Promise<CursorExecHandlerResult<PiWriteExecResult>>;
|
|
598
|
+
piGrep?: (call: CursorPiCall<PiGrepExecArgs>) => Promise<CursorExecHandlerResult<PiGrepExecResult>>;
|
|
599
|
+
piFind?: (call: CursorPiCall<PiFindExecArgs>) => Promise<CursorExecHandlerResult<PiFindExecResult>>;
|
|
600
|
+
piLs?: (call: CursorPiCall<PiLsExecArgs>) => Promise<CursorExecHandlerResult<PiLsExecResult>>;
|
|
561
601
|
onToolResult?: CursorToolResultHandler;
|
|
562
602
|
}
|
|
563
603
|
/**
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const kCursorExecResolved: unique symbol;
|
|
2
|
+
export type CursorExecResolvedCarrier = object & {
|
|
3
|
+
[kCursorExecResolved]?: true;
|
|
4
|
+
};
|
|
5
|
+
export declare function isCursorExecResolved(block: CursorExecResolvedCarrier | null | undefined): boolean;
|
|
6
|
+
export declare function copyCursorExecResolved(target: CursorExecResolvedCarrier, source: CursorExecResolvedCarrier): void;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Api, FetchImpl, Model, Provider } from "../../types";
|
|
2
|
+
/** Catalog identities are rendered and used for routing; unsafe values are dropped, never rewritten. */
|
|
3
|
+
export declare function isSafeCatalogModelId(value: unknown): value is string;
|
|
2
4
|
/**
|
|
3
5
|
* Minimal OpenAI-style model entry shape consumed by discovery.
|
|
4
6
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function getProviderStreamIdleTimeoutFallbackMs(provider: string): number | undefined;
|
|
2
|
+
export declare function isGrokModelId(modelId: string | undefined): boolean;
|
|
2
3
|
export declare function getProviderFirstEventTimeoutFallbackMs(provider: string): number | undefined;
|
|
3
4
|
/**
|
|
4
5
|
* Returns the idle timeout used for provider streaming transports.
|
|
@@ -16,8 +17,10 @@ export declare function getStreamIdleTimeoutMs(fallbackMs?: number): number | un
|
|
|
16
17
|
*
|
|
17
18
|
* Honors `GJC_OPENAI_STREAM_IDLE_TIMEOUT_MS` first (`PI_OPENAI_STREAM_IDLE_TIMEOUT_MS` is the legacy alias). Set `=0` to disable.
|
|
18
19
|
* When `provider` is given, long-reasoning hosts (xAI Grok and Grok Build) use that floor instead of the 120s default.
|
|
20
|
+
* Grok models reached through other OpenAI-compatible hosts (`openrouter/x-ai/grok-*`, kilo, litellm, …) get the
|
|
21
|
+
* same floor keyed on the model id, because long-reasoning silence is a property of the model (#4797).
|
|
19
22
|
*/
|
|
20
|
-
export declare function getOpenAIStreamIdleTimeoutMs(provider?: string): number | undefined;
|
|
23
|
+
export declare function getOpenAIStreamIdleTimeoutMs(provider?: string, modelId?: string): number | undefined;
|
|
21
24
|
/**
|
|
22
25
|
* Returns the timeout used while waiting for the first stream event.
|
|
23
26
|
* The first token can legitimately take longer than later inter-event gaps,
|
|
@@ -47,7 +50,7 @@ export declare function getStreamFirstEventTimeoutMs(idleTimeoutMs?: number, fal
|
|
|
47
50
|
* window so a short post-connect first-event budget cannot kill legitimate
|
|
48
51
|
* slow setup.
|
|
49
52
|
*/
|
|
50
|
-
export declare function resolveOpenAISdkRequestTimeoutMs(provider: string, streamFirstEventTimeoutOverride?: number): number | undefined;
|
|
53
|
+
export declare function resolveOpenAISdkRequestTimeoutMs(provider: string, streamFirstEventTimeoutOverride?: number, modelId?: string): number | undefined;
|
|
51
54
|
/**
|
|
52
55
|
* Resolves the Anthropic SDK client `timeout` so stalled-before-headers requests
|
|
53
56
|
* are bounded. The Anthropic first-event watchdog deliberately arms only once
|
|
@@ -4,15 +4,9 @@
|
|
|
4
4
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
5
5
|
/** Test seam: the OAuth host as resolved from trusted env. */
|
|
6
6
|
export declare function resolveKimiOAuthHostForTest(): string;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"X-Msh-Version": string;
|
|
11
|
-
"X-Msh-Device-Name": string;
|
|
12
|
-
"X-Msh-Device-Model": string;
|
|
13
|
-
"X-Msh-Os-Version": string;
|
|
14
|
-
"X-Msh-Device-Id": string;
|
|
15
|
-
}>;
|
|
7
|
+
/** @internal Exported for tests. Builds unsanitized-input-safe Kimi common headers. */
|
|
8
|
+
export declare function buildKimiCommonHeaders(): Readonly<Record<string, string>>;
|
|
9
|
+
export declare const getKimiCommonHeaders: () => Readonly<Record<string, string>>;
|
|
16
10
|
/**
|
|
17
11
|
* Login with Kimi Code OAuth (device code flow).
|
|
18
12
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const loginOpenRouter: (options: import("./types").OAuthController) => Promise<string>;
|
|
@@ -7,7 +7,7 @@ export type OAuthCredentials = {
|
|
|
7
7
|
email?: string;
|
|
8
8
|
accountId?: string;
|
|
9
9
|
};
|
|
10
|
-
export type OAuthProvider = "kiro" | "alibaba-token-plan" | "anthropic" | "bizrouter" | "mara" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "deepinfra" | "fireworks" | "firepass" | "fugu" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "opengateway" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "opencodex" | "zai";
|
|
10
|
+
export type OAuthProvider = "kiro" | "alibaba-token-plan" | "anthropic" | "bizrouter" | "mara" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "deepinfra" | "fireworks" | "firepass" | "fugu" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "opengateway" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "opencodex" | "zai";
|
|
11
11
|
export type OAuthProviderId = OAuthProvider | (string & {});
|
|
12
12
|
export type OAuthPrompt = {
|
|
13
13
|
message: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/ai",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.0",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo and Gajae Code Contributors",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@anthropic-ai/sdk": "^0.94.0",
|
|
42
42
|
"@bufbuild/protobuf": "^2.12.0",
|
|
43
|
-
"@gajae-code/natives": "0.
|
|
44
|
-
"@gajae-code/utils": "0.
|
|
43
|
+
"@gajae-code/natives": "0.15.0",
|
|
44
|
+
"@gajae-code/utils": "0.15.0",
|
|
45
45
|
"openai": "^6.36.0",
|
|
46
46
|
"partial-json": "^0.1.7",
|
|
47
47
|
"zod": "4.4.3"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@types/bun": "^1.3.14"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
|
-
"bun": ">=1.
|
|
53
|
+
"bun": ">=1.4.0"
|
|
54
54
|
},
|
|
55
55
|
"files": [
|
|
56
56
|
"src",
|
|
@@ -75,7 +75,18 @@ const BACKGROUND_BACKOFF_MAX_MS = 30_000;
|
|
|
75
75
|
const PRESENTATION_FRESH_MS = 5 * 60_000;
|
|
76
76
|
const PRESENTATION_RETENTION_MS = 24 * 60 * 60_000;
|
|
77
77
|
const PRESENTATION_SIDECAR_VERSION = 1;
|
|
78
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Default location of the redacted presentation sidecar.
|
|
80
|
+
*
|
|
81
|
+
* Derived when a store is constructed, never at import time: the trusted
|
|
82
|
+
* config root is call-time state (#4761, #4772), so an import-time constant
|
|
83
|
+
* keeps pointing at the home that was in effect when this module first loaded
|
|
84
|
+
* and a process can read and write one logical profile through two different
|
|
85
|
+
* roots (#4786).
|
|
86
|
+
*/
|
|
87
|
+
function defaultPresentationSidecarPath(): string {
|
|
88
|
+
return path.join(getConfigRootDir(), "auth-broker-presentations.json");
|
|
89
|
+
}
|
|
79
90
|
|
|
80
91
|
function emptySnapshot(): SnapshotResponse {
|
|
81
92
|
return {
|
|
@@ -194,7 +205,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
194
205
|
constructor(opts: RemoteAuthCredentialStoreOptions) {
|
|
195
206
|
this.#client = opts.client;
|
|
196
207
|
this.#streamSnapshots = opts.streamSnapshots ?? true;
|
|
197
|
-
this.#presentationPath = opts.presentationPath ??
|
|
208
|
+
this.#presentationPath = opts.presentationPath ?? defaultPresentationSidecarPath();
|
|
198
209
|
this.#presentationAuthority = createHash("sha256").update(this.#client.baseUrl).digest("hex");
|
|
199
210
|
this.#applySnapshot(opts.initialSnapshot ?? emptySnapshot(), opts.initialSnapshot?.generation ?? 0, false);
|
|
200
211
|
this.#setInventoryState("pending", this.#generation, {
|
package/src/auth-storage.ts
CHANGED
|
@@ -1676,12 +1676,12 @@ export class AuthStorage {
|
|
|
1676
1676
|
return this.#store.listCredentialInventory?.(provider) ?? [];
|
|
1677
1677
|
}
|
|
1678
1678
|
|
|
1679
|
-
/** Return local
|
|
1679
|
+
/** Return local credential hard-removal action targets, including disabled rows. */
|
|
1680
1680
|
listCredentialRemovalTargets(provider?: string): CredentialRemovalTarget[] {
|
|
1681
1681
|
return this.#store.listCredentialRemovalTargets?.(provider) ?? [];
|
|
1682
1682
|
}
|
|
1683
1683
|
|
|
1684
|
-
/** Remove selected local
|
|
1684
|
+
/** Remove selected local credential rows atomically; conflict leaves all rows intact. */
|
|
1685
1685
|
removeAuthCredentialsHard(
|
|
1686
1686
|
provider: string,
|
|
1687
1687
|
targets: readonly CredentialRemovalTarget[],
|
|
@@ -3065,6 +3065,12 @@ export class AuthStorage {
|
|
|
3065
3065
|
await saveApiKeyCredential(apiKey);
|
|
3066
3066
|
return;
|
|
3067
3067
|
}
|
|
3068
|
+
case "openrouter": {
|
|
3069
|
+
const { loginOpenRouter } = await import("./utils/oauth/openrouter");
|
|
3070
|
+
const apiKey = await loginOpenRouter(ctrl);
|
|
3071
|
+
await saveApiKeyCredential(apiKey);
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3068
3074
|
case "litellm": {
|
|
3069
3075
|
const { loginLiteLLM } = await import("./utils/oauth/litellm");
|
|
3070
3076
|
const apiKey = await loginLiteLLM(ctrl);
|
|
@@ -6153,9 +6159,7 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
6153
6159
|
provider === undefined
|
|
6154
6160
|
? (this.#listAllStmt.all() as AuthRow[])
|
|
6155
6161
|
: (this.#listAllByProviderStmt.all(provider) as AuthRow[]);
|
|
6156
|
-
return rows
|
|
6157
|
-
.filter(row => row.credential_type === "oauth")
|
|
6158
|
-
.map(row => ({ id: row.id, provider: row.provider, expectedRevision: row.revision }));
|
|
6162
|
+
return rows.map(row => ({ id: row.id, provider: row.provider, expectedRevision: row.revision }));
|
|
6159
6163
|
}
|
|
6160
6164
|
removeAuthCredentialsHard(
|
|
6161
6165
|
provider: string,
|
|
@@ -6170,12 +6174,7 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
6170
6174
|
.get(target.id) as
|
|
6171
6175
|
| { id?: number; provider?: string; credential_type?: string; revision?: number }
|
|
6172
6176
|
| undefined;
|
|
6173
|
-
if (
|
|
6174
|
-
!row ||
|
|
6175
|
-
row.provider !== provider ||
|
|
6176
|
-
row.credential_type !== "oauth" ||
|
|
6177
|
-
row.revision !== target.expectedRevision
|
|
6178
|
-
) {
|
|
6177
|
+
if (!row || row.provider !== provider || row.revision !== target.expectedRevision) {
|
|
6179
6178
|
currentIds.push(row?.id ?? target.id);
|
|
6180
6179
|
}
|
|
6181
6180
|
}
|
package/src/model-cache.ts
CHANGED
|
@@ -156,3 +156,81 @@ export function writeModelCache<TApi extends Api>(
|
|
|
156
156
|
// Cache writes are best-effort; failures should not break model resolution.
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
export function insertModelCacheIfAbsent<TApi extends Api>(
|
|
161
|
+
providerId: string,
|
|
162
|
+
updatedAt: number,
|
|
163
|
+
models: Model<TApi>[],
|
|
164
|
+
authoritative: boolean,
|
|
165
|
+
staticFingerprint: string,
|
|
166
|
+
dbPath?: string,
|
|
167
|
+
dynamicModelIds?: readonly string[],
|
|
168
|
+
dynamicModelProvenance?: string,
|
|
169
|
+
): boolean {
|
|
170
|
+
try {
|
|
171
|
+
const result = getDb(dbPath).run(
|
|
172
|
+
`INSERT INTO model_cache (provider_id, version, updated_at, authoritative, static_fingerprint, dynamic_model_ids, dynamic_model_provenance, models)
|
|
173
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
174
|
+
ON CONFLICT(provider_id) DO NOTHING`,
|
|
175
|
+
[
|
|
176
|
+
providerId,
|
|
177
|
+
CACHE_SCHEMA_VERSION,
|
|
178
|
+
updatedAt,
|
|
179
|
+
authoritative ? 1 : 0,
|
|
180
|
+
staticFingerprint,
|
|
181
|
+
dynamicModelIds === undefined ? null : JSON.stringify(dynamicModelIds),
|
|
182
|
+
dynamicModelProvenance ?? null,
|
|
183
|
+
JSON.stringify(models),
|
|
184
|
+
],
|
|
185
|
+
);
|
|
186
|
+
return result.changes === 1;
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function updateModelCacheIfUnchanged<TApi extends Api>(
|
|
193
|
+
providerId: string,
|
|
194
|
+
expectedUpdatedAt: number,
|
|
195
|
+
expectedDynamicModelIds: readonly string[] | undefined,
|
|
196
|
+
expectedDynamicModelProvenance: string | undefined,
|
|
197
|
+
expectedModels: readonly Model<TApi>[],
|
|
198
|
+
updatedAt: number,
|
|
199
|
+
models: Model<TApi>[],
|
|
200
|
+
authoritative: boolean,
|
|
201
|
+
staticFingerprint: string,
|
|
202
|
+
dbPath?: string,
|
|
203
|
+
dynamicModelIds?: readonly string[],
|
|
204
|
+
dynamicModelProvenance?: string,
|
|
205
|
+
): boolean {
|
|
206
|
+
try {
|
|
207
|
+
const expectedIds = expectedDynamicModelIds === undefined ? null : JSON.stringify(expectedDynamicModelIds);
|
|
208
|
+
const provenance = expectedDynamicModelProvenance ?? null;
|
|
209
|
+
const nextIds = dynamicModelIds === undefined ? null : JSON.stringify(dynamicModelIds);
|
|
210
|
+
const nextProvenance = dynamicModelProvenance ?? null;
|
|
211
|
+
const expectedModelsJson = JSON.stringify(expectedModels);
|
|
212
|
+
const result = getDb(dbPath).run(
|
|
213
|
+
`UPDATE model_cache
|
|
214
|
+
SET updated_at = ?, authoritative = ?, static_fingerprint = ?, dynamic_model_ids = ?,
|
|
215
|
+
dynamic_model_provenance = ?, models = ?
|
|
216
|
+
WHERE provider_id = ? AND updated_at = ?
|
|
217
|
+
AND dynamic_model_ids IS ? AND dynamic_model_provenance IS ? AND models = ?`,
|
|
218
|
+
[
|
|
219
|
+
updatedAt,
|
|
220
|
+
authoritative ? 1 : 0,
|
|
221
|
+
staticFingerprint,
|
|
222
|
+
nextIds,
|
|
223
|
+
nextProvenance,
|
|
224
|
+
JSON.stringify(models),
|
|
225
|
+
providerId,
|
|
226
|
+
expectedUpdatedAt,
|
|
227
|
+
expectedIds,
|
|
228
|
+
provenance,
|
|
229
|
+
expectedModelsJson,
|
|
230
|
+
],
|
|
231
|
+
);
|
|
232
|
+
return result.changes === 1;
|
|
233
|
+
} catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|