@gajae-code/ai 0.16.3 → 0.16.6

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 (50) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/types/auth-gateway/server.d.ts +4 -2
  3. package/dist/types/bedrock-claude-cache-policy.d.ts +21 -0
  4. package/dist/types/openai-completions-compat.d.ts +22 -0
  5. package/dist/types/providers/amazon-bedrock.d.ts +17 -2
  6. package/dist/types/providers/anthropic.d.ts +1 -0
  7. package/dist/types/providers/openai-completions-compat.d.ts +1 -1
  8. package/dist/types/providers/opencode-go-session.d.ts +5 -0
  9. package/dist/types/types.d.ts +9 -2
  10. package/dist/types/utils/codex-entitlement.d.ts +10 -0
  11. package/dist/types/utils/provider-response.d.ts +1 -0
  12. package/package.json +3 -3
  13. package/src/auth-gateway/server.ts +5 -0
  14. package/src/auth-storage.ts +17 -8
  15. package/src/bedrock-claude-cache-policy.d.ts +21 -0
  16. package/src/bedrock-claude-cache-policy.ts +68 -0
  17. package/src/model-pricing.ts +11 -0
  18. package/src/model-thinking.ts +43 -8
  19. package/src/models.json +37 -0
  20. package/src/openai-completions-compat.d.ts +22 -0
  21. package/src/openai-completions-compat.ts +77 -3
  22. package/src/providers/amazon-bedrock.d.ts +17 -2
  23. package/src/providers/amazon-bedrock.ts +19 -11
  24. package/src/providers/anthropic.d.ts +1 -0
  25. package/src/providers/anthropic.ts +44 -22
  26. package/src/providers/azure-openai-responses.ts +5 -2
  27. package/src/providers/cursor.ts +12 -6
  28. package/src/providers/google-gemini-cli.ts +6 -1
  29. package/src/providers/google-shared.ts +1 -1
  30. package/src/providers/kiro-api-key.ts +5 -2
  31. package/src/providers/kiro-codewhisperer.ts +10 -2
  32. package/src/providers/mock.ts +1 -0
  33. package/src/providers/ollama.ts +1 -1
  34. package/src/providers/openai-codex/response-handler.ts +1 -1
  35. package/src/providers/openai-codex-responses.ts +10 -2
  36. package/src/providers/openai-completions-compat.d.ts +1 -1
  37. package/src/providers/openai-completions-compat.ts +8 -1
  38. package/src/providers/openai-completions.ts +25 -3
  39. package/src/providers/openai-responses.ts +21 -3
  40. package/src/providers/opencode-go-session.d.ts +5 -0
  41. package/src/providers/opencode-go-session.ts +57 -0
  42. package/src/stream.ts +1 -0
  43. package/src/types.d.ts +9 -2
  44. package/src/types.ts +9 -0
  45. package/src/utils/codex-entitlement.d.ts +10 -0
  46. package/src/utils/codex-entitlement.ts +21 -0
  47. package/src/utils/discovery/antigravity.ts +2 -1
  48. package/src/utils/discovery/gemini.ts +2 -1
  49. package/src/utils/provider-response.d.ts +1 -0
  50. package/src/utils/provider-response.ts +9 -2
@@ -93,6 +93,11 @@ import {
93
93
  processResponsesStream,
94
94
  repairOrphanResponsesToolOutputs,
95
95
  } from "./openai-responses-shared";
96
+ import {
97
+ applyOpenCodeGoSessionHeader,
98
+ resolveOpenCodeGoSessionId,
99
+ wrapFetchForOpenCodeGoSession,
100
+ } from "./opencode-go-session";
96
101
  import { transformMessages } from "./transform-messages";
97
102
 
98
103
  /**
@@ -378,6 +383,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
378
383
  options?.headers,
379
384
  options?.initiatorOverride,
380
385
  cacheSessionId,
386
+ options?.providerSessionId,
381
387
  cacheRetention,
382
388
  options?.onSseEvent,
383
389
  options?.fetch,
@@ -389,9 +395,17 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
389
395
  );
390
396
  const premiumRequestsTotal = copilotPremiumRequests;
391
397
  const providerSessionState = getOpenAIResponsesProviderSessionState(model, options?.providerSessionState);
392
- const { params } = buildParams(model, context, options, providerSessionState, cacheRetention, baseUrl);
398
+ let { params } = buildParams(model, context, options, providerSessionState, cacheRetention, baseUrl);
393
399
  const idleTimeoutMs = options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs(model.provider, model.id);
394
- options?.onPayload?.(params, undefined, options?.attemptScope);
400
+ const replacementPayload = await options?.onPayload?.(
401
+ params,
402
+ undefined,
403
+ options?.attemptScope,
404
+ options?.signal,
405
+ );
406
+ if (replacementPayload !== undefined) {
407
+ params = replacementPayload as typeof params;
408
+ }
395
409
  rawRequestDump = {
396
410
  provider: model.provider,
397
411
  api: output.api,
@@ -556,6 +570,7 @@ function createClient(
556
570
  extraHeaders?: Record<string, string>,
557
571
  initiatorOverride?: MessageAttribution,
558
572
  sessionId?: string,
573
+ providerSessionId?: string,
559
574
  cacheRetention?: CacheRetention,
560
575
  onSseEvent?: OpenAIResponsesOptions["onSseEvent"],
561
576
  fetchOverride?: FetchImpl,
@@ -618,6 +633,8 @@ function createClient(
618
633
  }
619
634
  headers = applyOpenAIRequestTransformHeaders(headers, model.requestTransform, `Gajae-Code/${packageJson.version}`);
620
635
  const { baseUrl: clientBaseUrl, query: endpointQuery } = splitBaseUrlQuery(baseUrl);
636
+ const openCodeGoSessionId = resolveOpenCodeGoSessionId(model, baseUrl, providerSessionId, "openai");
637
+ headers = applyOpenCodeGoSessionHeader(headers, openCodeGoSessionId);
621
638
  const baseFetch = fetchOverride ?? fetch;
622
639
  const queryFetch = Object.assign(
623
640
  async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
@@ -626,8 +643,9 @@ function createClient(
626
643
  baseFetch.preconnect ? { preconnect: baseFetch.preconnect } : {},
627
644
  );
628
645
  const boundedFetch = wrapOpenAIFetchForBoundedRateLimits(queryFetch, maxRetryDelayMs);
646
+ const providerScopedFetch = wrapFetchForOpenCodeGoSession(boundedFetch, openCodeGoSessionId);
629
647
  const transformedFetch = wrapFetchForOpenAIRequestTransform(
630
- boundedFetch,
648
+ providerScopedFetch,
631
649
  model.requestTransform,
632
650
  `Gajae-Code/${packageJson.version}`,
633
651
  );
@@ -0,0 +1,5 @@
1
+ import type { FetchImpl, Model } from "../types";
2
+ export type OpenCodeGoApiFamily = "openai" | "anthropic";
3
+ export declare function resolveOpenCodeGoSessionId(model: Pick<Model, "provider">, baseUrl: string | undefined, providerSessionId: string | undefined, apiFamily: OpenCodeGoApiFamily): string | undefined;
4
+ export declare function applyOpenCodeGoSessionHeader(headers: Record<string, string>, sessionId: string | undefined): Record<string, string>;
5
+ export declare function wrapFetchForOpenCodeGoSession(baseFetch: FetchImpl, sessionId: string | undefined): FetchImpl;
@@ -0,0 +1,57 @@
1
+ import type { FetchImpl, Model } from "../types";
2
+
3
+ const OPENCODE_GO_ORIGIN = "https://opencode.ai";
4
+ const OPENCODE_GO_OPENAI_BASE_PATH = "/zen/go/v1";
5
+ const OPENCODE_GO_ANTHROPIC_BASE_PATH = "/zen/go";
6
+
7
+ export type OpenCodeGoApiFamily = "openai" | "anthropic";
8
+
9
+ function expectedBasePath(apiFamily: OpenCodeGoApiFamily): string {
10
+ return apiFamily === "openai" ? OPENCODE_GO_OPENAI_BASE_PATH : OPENCODE_GO_ANTHROPIC_BASE_PATH;
11
+ }
12
+
13
+ export function resolveOpenCodeGoSessionId(
14
+ model: Pick<Model, "provider">,
15
+ baseUrl: string | undefined,
16
+ providerSessionId: string | undefined,
17
+ apiFamily: OpenCodeGoApiFamily,
18
+ ): string | undefined {
19
+ if (model.provider !== "opencode-go" || !baseUrl || !providerSessionId) return undefined;
20
+ try {
21
+ const url = new URL(baseUrl);
22
+ if (url.origin !== OPENCODE_GO_ORIGIN) return undefined;
23
+ if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "") return undefined;
24
+ if (url.pathname.replace(/\/+$/u, "") !== expectedBasePath(apiFamily)) return undefined;
25
+ return providerSessionId;
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ }
30
+
31
+ export function applyOpenCodeGoSessionHeader(
32
+ headers: Record<string, string>,
33
+ sessionId: string | undefined,
34
+ ): Record<string, string> {
35
+ const normalizedHeaders = new Headers(headers);
36
+ normalizedHeaders.delete("x-opencode-session");
37
+ if (sessionId) normalizedHeaders.set("x-opencode-session", sessionId);
38
+ return Object.fromEntries(normalizedHeaders.entries());
39
+ }
40
+
41
+ export function wrapFetchForOpenCodeGoSession(baseFetch: FetchImpl, sessionId: string | undefined): FetchImpl {
42
+ return Object.assign(
43
+ async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
44
+ if (input instanceof Request) {
45
+ const request = new Request(input, init);
46
+ request.headers.delete("x-opencode-session");
47
+ if (sessionId) request.headers.set("x-opencode-session", sessionId);
48
+ return baseFetch(request);
49
+ }
50
+ const headers = new Headers(init?.headers);
51
+ headers.delete("x-opencode-session");
52
+ if (sessionId) headers.set("x-opencode-session", sessionId);
53
+ return baseFetch(input, { ...init, headers });
54
+ },
55
+ baseFetch.preconnect ? { preconnect: baseFetch.preconnect } : {},
56
+ );
57
+ }
package/src/stream.ts CHANGED
@@ -955,6 +955,7 @@ function mapOptionsForApi<TApi extends Api>(
955
955
  streamMaxRetries: options?.fallbackManaged ? 0 : options?.streamMaxRetries,
956
956
  metadata: options?.metadata,
957
957
  sessionId: options?.sessionId,
958
+ providerSessionId: options?.providerSessionId,
958
959
  providerSessionState: options?.providerSessionState,
959
960
  onPayload: options?.onPayload,
960
961
  onResponse: options?.onResponse,
package/src/types.d.ts CHANGED
@@ -239,6 +239,13 @@ export interface StreamOptions {
239
239
  * session-aware features. Ignored by providers that don't support it.
240
240
  */
241
241
  sessionId?: string;
242
+ /**
243
+ * Opaque conversation identity owned by the calling agent/session lifecycle.
244
+ * Unlike `sessionId`, this MUST NOT be synthesized from prompts, credentials,
245
+ * paths, cache keys, or other request content. Providers with a dedicated
246
+ * conversation header may use this only when their endpoint policy permits it.
247
+ */
248
+ providerSessionId?: string;
242
249
  /**
243
250
  * Provider-scoped mutable state store for this agent session.
244
251
  * Providers can use this to persist transport/session state between turns.
@@ -249,12 +256,12 @@ export interface StreamOptions {
249
256
  * Return undefined to keep the payload unchanged.
250
257
  * The `scope` parameter carries the per-attempt identity for execution attribution.
251
258
  */
252
- onPayload?: (payload: unknown, model?: Model<Api>, scope?: AttemptScopeRef) => unknown | undefined | Promise<unknown | undefined>;
259
+ onPayload?: (payload: unknown, model?: Model<Api>, scope?: AttemptScopeRef, signal?: AbortSignal) => unknown | undefined | Promise<unknown | undefined>;
253
260
  /**
254
261
  * Optional callback for provider response metadata after headers are received.
255
262
  * The `scope` parameter carries the per-attempt identity for execution attribution.
256
263
  */
257
- onResponse?: (response: ProviderResponseMetadata, model?: Model<Api>, scope?: AttemptScopeRef) => void | Promise<void>;
264
+ onResponse?: (response: ProviderResponseMetadata, model?: Model<Api>, scope?: AttemptScopeRef, signal?: AbortSignal) => void | Promise<void>;
258
265
  /**
259
266
  * Internal dispatch-admission hook. Providers invoke this immediately before
260
267
  * submitting an outbound request; stream forwarding retains a first-response
package/src/types.ts CHANGED
@@ -443,6 +443,13 @@ export interface StreamOptions {
443
443
  * session-aware features. Ignored by providers that don't support it.
444
444
  */
445
445
  sessionId?: string;
446
+ /**
447
+ * Opaque conversation identity owned by the calling agent/session lifecycle.
448
+ * Unlike `sessionId`, this MUST NOT be synthesized from prompts, credentials,
449
+ * paths, cache keys, or other request content. Providers with a dedicated
450
+ * conversation header may use this only when their endpoint policy permits it.
451
+ */
452
+ providerSessionId?: string;
446
453
  /**
447
454
  * Provider-scoped mutable state store for this agent session.
448
455
  * Providers can use this to persist transport/session state between turns.
@@ -457,6 +464,7 @@ export interface StreamOptions {
457
464
  payload: unknown,
458
465
  model?: Model<Api>,
459
466
  scope?: AttemptScopeRef,
467
+ signal?: AbortSignal,
460
468
  ) => unknown | undefined | Promise<unknown | undefined>;
461
469
  /**
462
470
  * Optional callback for provider response metadata after headers are received.
@@ -466,6 +474,7 @@ export interface StreamOptions {
466
474
  response: ProviderResponseMetadata,
467
475
  model?: Model<Api>,
468
476
  scope?: AttemptScopeRef,
477
+ signal?: AbortSignal,
469
478
  ) => void | Promise<void>;
470
479
  /**
471
480
  * Internal dispatch-admission hook. Providers invoke this immediately before
@@ -6,6 +6,16 @@
6
6
  * authority for the account tier; this module only names the model policy and
7
7
  * keeps the provider's deterministic rejection wording in one place.
8
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;
9
19
  export declare function requiresOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
10
20
  export declare function requiresStrictOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
11
21
  export declare function isOpenAICodexChatGPTEntitlementError(message: string | undefined, code?: string): boolean;
@@ -7,6 +7,27 @@
7
7
  * keeps the provider's deterministic rejection wording in one place.
8
8
  */
9
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
+
10
31
  export function requiresOpenAICodexProModel(provider: string, modelId: string | undefined): boolean {
11
32
  return (
12
33
  provider === "openai-codex" &&
@@ -180,6 +180,7 @@ export async function fetchAntigravityDiscoveryModels(
180
180
  options: FetchAntigravityDiscoveryModelsOptions,
181
181
  ): Promise<Model<"google-gemini-cli">[] | null> {
182
182
  const fetcher = options.fetcher ?? fetch;
183
+ const signal = options.signal ?? AbortSignal.timeout(5_000);
183
184
  const targetProvider = options.targetProvider ?? "google-antigravity";
184
185
  const endpoints = options.endpoint
185
186
  ? [trimTrailingSlashes(options.endpoint)]
@@ -196,7 +197,7 @@ export async function fetchAntigravityDiscoveryModels(
196
197
  "User-Agent": options.userAgent ?? getAntigravityUserAgent(),
197
198
  },
198
199
  body: JSON.stringify({}),
199
- signal: options.signal,
200
+ signal,
200
201
  });
201
202
  } catch {
202
203
  continue;
@@ -72,6 +72,7 @@ export async function fetchGeminiModels(
72
72
  const baseUrl = normalizeBaseUrl(options.baseUrl);
73
73
  const pageSize = normalizePositiveInt(options.pageSize, DEFAULT_PAGE_SIZE);
74
74
  const maxPages = normalizePositiveInt(options.maxPages, DEFAULT_MAX_PAGES);
75
+ const signal = options.signal ?? AbortSignal.timeout(5_000);
75
76
 
76
77
  const bundledById = new Map(
77
78
  getBundledModels("google").map(model => [model.id, model as Model<"google-generative-ai">]),
@@ -86,7 +87,7 @@ export async function fetchGeminiModels(
86
87
  try {
87
88
  response = await fetchImpl(requestUrl, {
88
89
  method: "GET",
89
- signal: options.signal,
90
+ signal,
90
91
  });
91
92
  } catch {
92
93
  return null;
@@ -3,4 +3,5 @@ export declare function normalizeProviderResponse(response: Response, requestId?
3
3
  export declare function notifyProviderResponse(options: {
4
4
  onResponse?: StreamOptions["onResponse"];
5
5
  attemptScope?: AttemptScopeRef;
6
+ signal?: AbortSignal;
6
7
  } | undefined, response: Response, model?: Model<Api>, requestId?: string | null, metadata?: Record<string, unknown>): Promise<void>;
@@ -19,12 +19,19 @@ export function normalizeProviderResponse(
19
19
  }
20
20
 
21
21
  export async function notifyProviderResponse(
22
- options: { onResponse?: StreamOptions["onResponse"]; attemptScope?: AttemptScopeRef } | undefined,
22
+ options:
23
+ | { onResponse?: StreamOptions["onResponse"]; attemptScope?: AttemptScopeRef; signal?: AbortSignal }
24
+ | undefined,
23
25
  response: Response,
24
26
  model?: Model<Api>,
25
27
  requestId?: string | null,
26
28
  metadata?: Record<string, unknown>,
27
29
  ): Promise<void> {
28
30
  if (!options?.onResponse) return;
29
- await options.onResponse(normalizeProviderResponse(response, requestId, metadata), model, options.attemptScope);
31
+ await options.onResponse(
32
+ normalizeProviderResponse(response, requestId, metadata),
33
+ model,
34
+ options.attemptScope,
35
+ options.signal,
36
+ );
30
37
  }