@gajae-code/ai 0.12.5 → 0.12.7

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 CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.12.7] - 2026-07-31
6
+
7
+ ## [0.12.6] - 2026-07-31
8
+
5
9
  ## [0.12.5] - 2026-07-30
6
10
  ### Fixed
7
11
 
@@ -224,19 +224,21 @@ export interface StreamOptions {
224
224
  /**
225
225
  * Optional callback for inspecting or replacing provider payloads before sending.
226
226
  * Return undefined to keep the payload unchanged.
227
+ * The `scope` parameter carries the per-attempt identity for execution attribution.
227
228
  */
228
- onPayload?: (payload: unknown, model?: Model<Api>) => unknown | undefined | Promise<unknown | undefined>;
229
+ onPayload?: (payload: unknown, model?: Model<Api>, scope?: AttemptScopeRef) => unknown | undefined | Promise<unknown | undefined>;
229
230
  /**
230
231
  * Optional callback for provider response metadata after headers are received.
232
+ * The `scope` parameter carries the per-attempt identity for execution attribution.
231
233
  */
232
- onResponse?: (response: ProviderResponseMetadata, model?: Model<Api>) => void | Promise<void>;
234
+ onResponse?: (response: ProviderResponseMetadata, model?: Model<Api>, scope?: AttemptScopeRef) => void | Promise<void>;
233
235
  /**
234
236
  * Optional callback for raw Server-Sent Events as they arrive from HTTP streaming providers.
235
237
  *
236
238
  * Diagnostic only: provider implementations must ignore callback failures and must not
237
239
  * let observers alter stream contents.
238
240
  */
239
- onSseEvent?: (event: RawSseEvent, model?: Model<Api>) => void;
241
+ onSseEvent?: (event: RawSseEvent, model?: Model<Api>, scope?: AttemptScopeRef) => void;
240
242
  /**
241
243
  * Optional override for the first streamed event watchdog in milliseconds.
242
244
  * Set to 0 to disable the first-event watchdog for this request.
@@ -266,6 +268,22 @@ export interface StreamOptions {
266
268
  authCredentialType?: "api_key" | "oauth";
267
269
  /** Cursor exec/MCP tool handlers (cursor-agent only). */
268
270
  execHandlers?: CursorExecHandlers;
271
+ /** Per-attempt identity for execution attribution. Threaded into onPayload/onResponse calls. */
272
+ attemptScope?: AttemptScopeRef;
273
+ }
274
+ /**
275
+ * Low-level structural carrier for per-attempt identity attribution.
276
+ *
277
+ * Defined in `packages/ai` so that {@link SimpleStreamOptions} and provider
278
+ * hook signatures can carry an attempt identity without a reverse dependency
279
+ * on `packages/agent`. The concrete `AttemptScope` in `packages/agent` is
280
+ * structurally assignable to this interface (same `attemptId` + `generation`
281
+ * + `lineage` fields).
282
+ */
283
+ export interface AttemptScopeRef {
284
+ readonly attemptId: string;
285
+ readonly generation: number;
286
+ readonly lineage: string;
269
287
  }
270
288
  export interface SimpleStreamOptions extends StreamOptions {
271
289
  reasoning?: Effort;
@@ -1,3 +1,6 @@
1
- import type { Api, Model, ProviderResponseMetadata, StreamOptions } from "../types";
1
+ import type { Api, AttemptScopeRef, Model, ProviderResponseMetadata, StreamOptions } from "../types";
2
2
  export declare function normalizeProviderResponse(response: Response, requestId?: string | null, metadata?: Record<string, unknown>): ProviderResponseMetadata;
3
- export declare function notifyProviderResponse(options: Pick<StreamOptions, "onResponse"> | undefined, response: Response, model?: Model<Api>, requestId?: string | null, metadata?: Record<string, unknown>): Promise<void>;
3
+ export declare function notifyProviderResponse(options: {
4
+ onResponse?: StreamOptions["onResponse"];
5
+ attemptScope?: AttemptScopeRef;
6
+ } | undefined, response: Response, model?: Model<Api>, requestId?: string | null, metadata?: Record<string, unknown>): Promise<void>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.12.5",
4
+ "version": "0.12.7",
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,7 +40,7 @@
40
40
  "dependencies": {
41
41
  "@anthropic-ai/sdk": "^0.94.0",
42
42
  "@bufbuild/protobuf": "^2.12.0",
43
- "@gajae-code/utils": "0.12.5",
43
+ "@gajae-code/utils": "0.12.7",
44
44
  "openai": "^6.36.0",
45
45
  "partial-json": "^0.1.7",
46
46
  "zod": "4.4.3"
@@ -228,7 +228,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
228
228
  toolConfig,
229
229
  additionalModelRequestFields,
230
230
  };
231
- options?.onPayload?.(commandInput);
231
+ options?.onPayload?.(commandInput, model, options?.attemptScope);
232
232
 
233
233
  const host = `bedrock-runtime.${region}.amazonaws.com`;
234
234
  const url = `https://${host}/model/${encodeURIComponent(model.id)}/converse-stream`;
@@ -1348,7 +1348,9 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1348
1348
  dynamicHeaders: copilotDynamicHeaders?.headers,
1349
1349
  isOAuth: options?.isOAuth,
1350
1350
  hasTools: !!context.tools?.length,
1351
- onSseEvent: options?.onSseEvent,
1351
+ onSseEvent: options?.onSseEvent
1352
+ ? event => options.onSseEvent!(event, model, options?.attemptScope)
1353
+ : undefined,
1352
1354
  fetch: options?.fetch,
1353
1355
  requestMaxRetries: options?.requestMaxRetries,
1354
1356
  maxRetryDelayMs: options?.maxRetryDelayMs,
@@ -1386,7 +1388,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1386
1388
  if (dropFastMode) {
1387
1389
  dropAnthropicFastMode(nextParams);
1388
1390
  }
1389
- const replacementPayload = await options?.onPayload?.(nextParams, model);
1391
+ const replacementPayload = await options?.onPayload?.(nextParams, model, options?.attemptScope);
1390
1392
  if (replacementPayload !== undefined) {
1391
1393
  nextParams = replacementPayload as typeof nextParams;
1392
1394
  }
@@ -1489,7 +1491,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1489
1491
  } = await getAnthropicStreamResponse(
1490
1492
  anthropicRequest,
1491
1493
  requestSignal,
1492
- options?.client ? event => options?.onSseEvent?.(event, model) : undefined,
1494
+ options?.client ? event => options?.onSseEvent?.(event, model, options?.attemptScope) : undefined,
1493
1495
  );
1494
1496
  await notifyProviderResponse(options, response, model, requestId);
1495
1497
  let sawEvent = false;
@@ -126,7 +126,7 @@ export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses"
126
126
  const { baseUrl } = resolveAzureConfig(model, options);
127
127
  const params = buildParams(model, context, options, deploymentName, baseUrl);
128
128
  const idleTimeoutMs = options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs();
129
- options?.onPayload?.(params);
129
+ options?.onPayload?.(params, model, options?.attemptScope);
130
130
  rawRequestDump = {
131
131
  provider: model.provider,
132
132
  api: output.api,
@@ -312,7 +312,9 @@ function createClient(model: Model<"azure-openai-responses">, apiKey: string, op
312
312
  maxRetries: resolveRetryBudget(options?.requestMaxRetries, 5),
313
313
  defaultHeaders: headers,
314
314
  baseURL: baseUrl,
315
- fetch: onSseEvent ? wrapFetchForSseDebug(baseFetch, event => onSseEvent(event, model)) : baseFetch,
315
+ fetch: onSseEvent
316
+ ? wrapFetchForSseDebug(baseFetch, event => onSseEvent(event, model, options?.attemptScope))
317
+ : baseFetch,
316
318
  });
317
319
  }
318
320
 
@@ -2632,7 +2632,7 @@ function buildGrpcRequest(
2632
2632
  conversationId: state.conversationId,
2633
2633
  });
2634
2634
 
2635
- options?.onPayload?.(runRequest);
2635
+ options?.onPayload?.(runRequest, model, options?.attemptScope);
2636
2636
 
2637
2637
  // Tools are sent later via requestContext (exec handshake)
2638
2638
 
@@ -279,6 +279,7 @@ export function streamGitLabDuo(
279
279
  sessionId: options.sessionId,
280
280
  providerSessionState: options.providerSessionState,
281
281
  onPayload: options.onPayload,
282
+ attemptScope: options?.attemptScope,
282
283
  onResponse: options.onResponse,
283
284
  onSseEvent: options.onSseEvent,
284
285
  fetch: options.fetch,
@@ -316,6 +317,7 @@ export function streamGitLabDuo(
316
317
  sessionId: options.sessionId,
317
318
  providerSessionState: options.providerSessionState,
318
319
  onPayload: options.onPayload,
320
+ attemptScope: options?.attemptScope,
319
321
  onResponse: options.onResponse,
320
322
  onSseEvent: options.onSseEvent,
321
323
  fetch: options.fetch,
@@ -348,6 +350,7 @@ export function streamGitLabDuo(
348
350
  sessionId: options.sessionId,
349
351
  providerSessionState: options.providerSessionState,
350
352
  onPayload: options.onPayload,
353
+ attemptScope: options?.attemptScope,
351
354
  onResponse: options.onResponse,
352
355
  onSseEvent: options.onSseEvent,
353
356
  fetch: options.fetch,
@@ -350,7 +350,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
350
350
  const endpoints = baseUrl ? [baseUrl] : isAntigravity ? ANTIGRAVITY_ENDPOINT_FALLBACKS : [DEFAULT_ENDPOINT];
351
351
 
352
352
  let requestBody = buildRequest(model, context, projectId, options, isAntigravity);
353
- const replacementPayload = await options?.onPayload?.(requestBody, model);
353
+ const replacementPayload = await options?.onPayload?.(requestBody, model, options?.attemptScope);
354
354
  if (replacementPayload !== undefined) {
355
355
  requestBody = replacementPayload as typeof requestBody;
356
356
  }
@@ -483,7 +483,12 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
483
483
  for await (const chunk of readSseJson<CloudCodeAssistResponseChunk>(
484
484
  activeResponse.body!,
485
485
  options?.signal,
486
- event => options?.onSseEvent?.({ event: event.event, data: event.data, raw: [...event.raw] }, model),
486
+ event =>
487
+ options?.onSseEvent?.(
488
+ { event: event.event, data: event.data, raw: [...event.raw] },
489
+ model,
490
+ options?.attemptScope,
491
+ ),
487
492
  )) {
488
493
  const responseData = chunk.response;
489
494
  if (!responseData) continue;
@@ -835,7 +835,7 @@ export function streamGoogleGenAI<T extends "google-generative-ai" | "google-ver
835
835
  try {
836
836
  const plan = await prepare();
837
837
  let params = plan.params;
838
- const replacement = await options?.onPayload?.(params, model);
838
+ const replacement = await options?.onPayload?.(params, model, options?.attemptScope);
839
839
  if (replacement !== undefined) {
840
840
  params = replacement as GenerateContentParameters;
841
841
  }
@@ -915,7 +915,11 @@ export function streamGoogleGenAI<T extends "google-generative-ai" | "google-ver
915
915
  }
916
916
 
917
917
  const googleStream = readSseJson<GenerateContentResponse>(response.body, options?.signal, event =>
918
- options?.onSseEvent?.({ event: event.event, data: event.data, raw: [...event.raw] }, model),
918
+ options?.onSseEvent?.(
919
+ { event: event.event, data: event.data, raw: [...event.raw] },
920
+ model,
921
+ options?.attemptScope,
922
+ ),
919
923
  );
920
924
 
921
925
  stream.push({ type: "start", partial: output });
@@ -327,6 +327,7 @@ async function runMock(
327
327
  ...(response.responseRequestId !== undefined ? { requestId: response.responseRequestId } : {}),
328
328
  },
329
329
  model,
330
+ options.attemptScope,
330
331
  );
331
332
  } catch (err) {
332
333
  stream.fail(err);
@@ -406,7 +406,7 @@ export const streamOllama: StreamFunction<"ollama-chat"> = (
406
406
  const baseUrl = normalizeBaseUrl(model.baseUrl);
407
407
  let body = createChatBody(model, context, options);
408
408
  const sentForcedToolChoice = body.tool_choice === "required";
409
- const replacementPayload = await options.onPayload?.(body, model);
409
+ const replacementPayload = await options.onPayload?.(body, model, options?.attemptScope);
410
410
  if (replacementPayload !== undefined) {
411
411
  body = replacementPayload as typeof body;
412
412
  }
@@ -86,6 +86,7 @@ export function streamOpenAIAnthropicShim(
86
86
  headers: mergedHeaders,
87
87
  sessionId: options?.sessionId,
88
88
  onPayload: options?.onPayload,
89
+ attemptScope: options?.attemptScope,
89
90
  onResponse: options?.onResponse,
90
91
  onSseEvent: options?.onSseEvent,
91
92
  fetch: options?.fetch,
@@ -117,6 +118,7 @@ export function streamOpenAIAnthropicShim(
117
118
  headers: mergedHeaders,
118
119
  sessionId: options?.sessionId,
119
120
  onPayload: options?.onPayload,
121
+ attemptScope: options?.attemptScope,
120
122
  onResponse: options?.onResponse,
121
123
  onSseEvent: options?.onSseEvent,
122
124
  fetch: options?.fetch,
@@ -647,7 +647,7 @@ async function buildCodexRequestContext(
647
647
  const url = resolveCodexResponsesUrl(baseUrl);
648
648
  const promptCacheKey = normalizeOpenAIResponsesPromptCacheKey(options?.sessionId);
649
649
  const transformedBody = await buildTransformedCodexRequestBody(model, context, options);
650
- options?.onPayload?.(transformedBody);
650
+ options?.onPayload?.(transformedBody, model, options?.attemptScope);
651
651
 
652
652
  const requestHeaders = { ...(model.headers ?? {}), ...(options?.headers ?? {}) };
653
653
  const rawRequestDump: RawHttpRequestDump = {
@@ -881,7 +881,7 @@ async function openCodexSseTransport(
881
881
  body,
882
882
  state,
883
883
  requestSetup.requestSignal,
884
- event => options?.onSseEvent?.(event, model),
884
+ event => options?.onSseEvent?.(event, model, options?.attemptScope),
885
885
  options?.fetch,
886
886
  options,
887
887
  ),
@@ -482,6 +482,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
482
482
  options?.requestMaxRetries,
483
483
  options?.sessionId,
484
484
  options?.maxRetryDelayMs,
485
+ options?.attemptScope,
485
486
  );
486
487
  const premiumRequestsTotal = copilotPremiumRequests;
487
488
  getCapturedErrorResponse = captureErrorResponse;
@@ -504,7 +505,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
504
505
  effectiveToolStrictModeOverride,
505
506
  );
506
507
  appliedToolStrictMode = toolStrictMode;
507
- options?.onPayload?.(params);
508
+ options?.onPayload?.(params, undefined, options?.attemptScope);
508
509
  rawRequestDump = {
509
510
  provider: model.provider,
510
511
  api: output.api,
@@ -1023,6 +1024,7 @@ async function createClient(
1023
1024
  requestMaxRetries?: number,
1024
1025
  sessionId?: string,
1025
1026
  maxRetryDelayMs?: number,
1027
+ attemptScope?: import("../types.js").AttemptScopeRef,
1026
1028
  ): Promise<{
1027
1029
  client: OpenAI;
1028
1030
  copilotPremiumRequests: number | undefined;
@@ -1145,7 +1147,7 @@ async function createClient(
1145
1147
  `Gajae-Code/${packageJson.version}`,
1146
1148
  );
1147
1149
  const debugFetch = onSseEvent
1148
- ? wrapFetchForSseDebug(transformedFetch, event => onSseEvent(event, model))
1150
+ ? wrapFetchForSseDebug(transformedFetch, event => onSseEvent(event, model, attemptScope))
1149
1151
  : transformedFetch;
1150
1152
  // Bound HTTP request timeout to roughly the first-event watchdog window.
1151
1153
  // The OpenAI SDK's default is 10 minutes per attempt × `maxRetries`, which
@@ -284,12 +284,13 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
284
284
  options?.authCredentialType,
285
285
  options?.requestMaxRetries,
286
286
  options?.maxRetryDelayMs,
287
+ options?.attemptScope,
287
288
  );
288
289
  const premiumRequestsTotal = copilotPremiumRequests;
289
290
  const providerSessionState = getOpenAIResponsesProviderSessionState(model, options?.providerSessionState);
290
291
  const { params } = buildParams(model, context, options, providerSessionState, baseUrl);
291
292
  const idleTimeoutMs = options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs();
292
- options?.onPayload?.(params);
293
+ options?.onPayload?.(params, undefined, options?.attemptScope);
293
294
  rawRequestDump = {
294
295
  provider: model.provider,
295
296
  api: output.api,
@@ -433,6 +434,7 @@ function createClient(
433
434
  authCredentialType?: OpenAIResponsesOptions["authCredentialType"],
434
435
  requestMaxRetries?: number,
435
436
  maxRetryDelayMs?: number,
437
+ attemptScope?: import("../types.js").AttemptScopeRef,
436
438
  ): {
437
439
  client: OpenAI;
438
440
  copilotPremiumRequests: number | undefined;
@@ -502,7 +504,7 @@ function createClient(
502
504
  maxRetries: resolveRetryBudget(requestMaxRetries, 5),
503
505
  defaultHeaders: headers,
504
506
  fetch: onSseEvent
505
- ? wrapFetchForSseDebug(transformedFetch, event => onSseEvent(event, model))
507
+ ? wrapFetchForSseDebug(transformedFetch, event => onSseEvent(event, model, attemptScope))
506
508
  : transformedFetch,
507
509
  }),
508
510
  copilotPremiumRequests,
package/src/stream.ts CHANGED
@@ -705,6 +705,7 @@ function mapOptionsForApi<TApi extends Api>(
705
705
  onPayload: options?.onPayload,
706
706
  onResponse: options?.onResponse,
707
707
  onSseEvent: options?.onSseEvent,
708
+ attemptScope: options?.attemptScope,
708
709
  execHandlers: options?.execHandlers,
709
710
  [managedAttemptValidated]: hasValidatedManagedAttempt(options),
710
711
  };
package/src/types.ts CHANGED
@@ -385,19 +385,29 @@ export interface StreamOptions {
385
385
  /**
386
386
  * Optional callback for inspecting or replacing provider payloads before sending.
387
387
  * Return undefined to keep the payload unchanged.
388
+ * The `scope` parameter carries the per-attempt identity for execution attribution.
388
389
  */
389
- onPayload?: (payload: unknown, model?: Model<Api>) => unknown | undefined | Promise<unknown | undefined>;
390
+ onPayload?: (
391
+ payload: unknown,
392
+ model?: Model<Api>,
393
+ scope?: AttemptScopeRef,
394
+ ) => unknown | undefined | Promise<unknown | undefined>;
390
395
  /**
391
396
  * Optional callback for provider response metadata after headers are received.
397
+ * The `scope` parameter carries the per-attempt identity for execution attribution.
392
398
  */
393
- onResponse?: (response: ProviderResponseMetadata, model?: Model<Api>) => void | Promise<void>;
399
+ onResponse?: (
400
+ response: ProviderResponseMetadata,
401
+ model?: Model<Api>,
402
+ scope?: AttemptScopeRef,
403
+ ) => void | Promise<void>;
394
404
  /**
395
405
  * Optional callback for raw Server-Sent Events as they arrive from HTTP streaming providers.
396
406
  *
397
407
  * Diagnostic only: provider implementations must ignore callback failures and must not
398
408
  * let observers alter stream contents.
399
409
  */
400
- onSseEvent?: (event: RawSseEvent, model?: Model<Api>) => void;
410
+ onSseEvent?: (event: RawSseEvent, model?: Model<Api>, scope?: AttemptScopeRef) => void;
401
411
  /**
402
412
  * Optional override for the first streamed event watchdog in milliseconds.
403
413
  * Set to 0 to disable the first-event watchdog for this request.
@@ -427,6 +437,23 @@ export interface StreamOptions {
427
437
  authCredentialType?: "api_key" | "oauth";
428
438
  /** Cursor exec/MCP tool handlers (cursor-agent only). */
429
439
  execHandlers?: CursorExecHandlers;
440
+ /** Per-attempt identity for execution attribution. Threaded into onPayload/onResponse calls. */
441
+ attemptScope?: AttemptScopeRef;
442
+ }
443
+
444
+ /**
445
+ * Low-level structural carrier for per-attempt identity attribution.
446
+ *
447
+ * Defined in `packages/ai` so that {@link SimpleStreamOptions} and provider
448
+ * hook signatures can carry an attempt identity without a reverse dependency
449
+ * on `packages/agent`. The concrete `AttemptScope` in `packages/agent` is
450
+ * structurally assignable to this interface (same `attemptId` + `generation`
451
+ * + `lineage` fields).
452
+ */
453
+ export interface AttemptScopeRef {
454
+ readonly attemptId: string;
455
+ readonly generation: number;
456
+ readonly lineage: string;
430
457
  }
431
458
 
432
459
  // Unified options with reasoning passed to streamSimple() and completeSimple()
@@ -1,4 +1,4 @@
1
- import type { Api, Model, ProviderResponseMetadata, StreamOptions } from "../types";
1
+ import type { Api, AttemptScopeRef, Model, ProviderResponseMetadata, StreamOptions } from "../types";
2
2
 
3
3
  export function normalizeProviderResponse(
4
4
  response: Response,
@@ -19,12 +19,12 @@ export function normalizeProviderResponse(
19
19
  }
20
20
 
21
21
  export async function notifyProviderResponse(
22
- options: Pick<StreamOptions, "onResponse"> | undefined,
22
+ options: { onResponse?: StreamOptions["onResponse"]; attemptScope?: AttemptScopeRef } | undefined,
23
23
  response: Response,
24
24
  model?: Model<Api>,
25
25
  requestId?: string | null,
26
26
  metadata?: Record<string, unknown>,
27
27
  ): Promise<void> {
28
28
  if (!options?.onResponse) return;
29
- await options.onResponse(normalizeProviderResponse(response, requestId, metadata), model);
29
+ await options.onResponse(normalizeProviderResponse(response, requestId, metadata), model, options.attemptScope);
30
30
  }