@gajae-code/agent-core 0.15.5 → 0.15.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.15.6] - 2026-08-30
6
+
5
7
  ## [0.15.5] - 2026-08-29
6
8
 
7
9
  ## [0.15.4] - 2026-08-29
@@ -5,7 +5,7 @@ import { type AssistantMessage, type AssistantMessageEvent, type CursorExecHandl
5
5
  import type { AppendOnlyContextManager } from "./append-only-context";
6
6
  import type { AttemptRunHandle, AttemptScope } from "./attempt-scope";
7
7
  import type { HarmonyAuditEvent } from "./harmony-leak";
8
- import type { AgentEvent, AgentLoopConfig, AgentMessage, AgentState, AgentTool, AgentToolContext, ManagedLogicalRunId, RunCancellationDomainBridge, RunResourceLedger, RunTerminalRequest, StreamFn, ToolCallContext } from "./types";
8
+ import type { AgentEvent, AgentLoopConfig, AgentMessage, AgentMetadataResolverContext, AgentState, AgentTool, AgentToolContext, ManagedLogicalRunId, RunCancellationDomainBridge, RunResourceLedger, RunTerminalRequest, StreamFn, ToolCallContext } from "./types";
9
9
  /**
10
10
  * Whether persisted history ends at a point where a new model turn can resume.
11
11
  * Assistant-ended histories require an in-memory queued message and are handled
@@ -271,7 +271,7 @@ export declare class Agent {
271
271
  * only included for `"anthropic"` requests). Falls back to the static
272
272
  * {@link metadata} value when no resolver is set.
273
273
  */
274
- metadataForProvider(provider: string): Record<string, unknown> | undefined;
274
+ metadataForProvider(provider: string, model?: Model, transport?: AgentMetadataResolverContext["transport"]): Record<string, unknown> | undefined;
275
275
  /**
276
276
  * Install a function that resolves request metadata at call time. The
277
277
  * resolver receives the target provider string and can gate provider-specific
@@ -280,7 +280,7 @@ export declare class Agent {
280
280
  * credential. Pass `undefined` to clear and revert to the static
281
281
  * {@link metadata} value.
282
282
  */
283
- setMetadataResolver(resolver: ((provider: string) => Record<string, unknown> | undefined) | undefined): void;
283
+ setMetadataResolver(resolver: ((context: AgentMetadataResolverContext) => Record<string, unknown> | undefined) | undefined): void;
284
284
  /**
285
285
  * Read the active OpenTelemetry configuration. Returns `undefined` when
286
286
  * instrumentation is disabled. Callers spawning child runs (e.g. subagent
@@ -358,6 +358,18 @@ export declare class Agent {
358
358
  setProvisionalAssistantMessageEventInterceptor(fn: ((message: AssistantMessage, event: AssistantMessageEvent) => void) | undefined): void;
359
359
  setOnBeforeYield(fn: (() => Promise<void> | void) | undefined): void;
360
360
  setShouldPause(fn: AgentLoopConfig["shouldPause"] | undefined): void;
361
+ /** The currently installed cooperative pause checkpoint, if any. */
362
+ get shouldPause(): AgentLoopConfig["shouldPause"] | undefined;
363
+ /**
364
+ * Fence old-turn steering admission.
365
+ *
366
+ * The loop polls steering UPSTREAM of its pause checkpoint (and again on the
367
+ * immediate-interrupt path), so a cooperative stop alone cannot prevent one
368
+ * more old-turn model call once a steering message has already been dequeued.
369
+ * While the fence returns true the poll yields no messages AND does not
370
+ * dequeue, so the queue survives intact for the next turn.
371
+ */
372
+ setSteeringAdmissionFence(fn: (() => boolean) | undefined): void;
361
373
  setMaintainContext(fn: AgentLoopConfig["maintainContext"] | undefined): void;
362
374
  /**
363
375
  * Publish an event produced OUTSIDE the agent loop (a provider that executed the tool
@@ -6,6 +6,20 @@ import type { AgentRunCoverage, AgentRunSummary } from "./run-collector";
6
6
  import type { AgentTelemetryConfig } from "./telemetry";
7
7
  /** Stream function - can return sync or Promise for async config lookup */
8
8
  export type StreamFn = (...args: Parameters<typeof streamSimple>) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
9
+ /**
10
+ * Request context supplied to provider-aware metadata resolvers.
11
+ *
12
+ * The model is the exact model selected for the concrete request (including
13
+ * fallback and ephemeral requests), while `transport` distinguishes the
14
+ * built-in stream path from a caller-supplied stream function. Metadata that
15
+ * carries provider identity must use both values to fail closed when routing
16
+ * is not the canonical provider path.
17
+ */
18
+ export interface AgentMetadataResolverContext {
19
+ provider: string;
20
+ model?: Model;
21
+ transport?: "default" | "custom";
22
+ }
9
23
  /** Stable identifier for a managed logical run, shared by all of its retry attempts. */
10
24
  export type ManagedLogicalRunId = number;
11
25
  /** A resource owned by a prompt run until its promise settles. */
@@ -233,7 +247,7 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
233
247
  * current when `AgentLoopConfig` was first constructed). Overrides the static
234
248
  * `metadata` field when present.
235
249
  */
236
- metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
250
+ metadataResolver?: (context: AgentMetadataResolverContext) => Record<string, unknown> | undefined;
237
251
  /**
238
252
  * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.
239
253
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/agent-core",
4
- "version": "0.15.5",
4
+ "version": "0.15.6",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -32,9 +32,9 @@
32
32
  "fmt": "biome format --write ."
33
33
  },
34
34
  "dependencies": {
35
- "@gajae-code/ai": "0.15.5",
36
- "@gajae-code/natives": "0.15.5",
37
- "@gajae-code/utils": "0.15.5",
35
+ "@gajae-code/ai": "0.15.6",
36
+ "@gajae-code/natives": "0.15.6",
37
+ "@gajae-code/utils": "0.15.6",
38
38
  "@opentelemetry/api": "^1.9.0"
39
39
  },
40
40
  "devDependencies": {
package/src/agent-loop.ts CHANGED
@@ -4523,7 +4523,13 @@ async function streamAssistantResponse(
4523
4523
  // reflects the credential actually used, not the snapshot from AgentLoopConfig construction.
4524
4524
  const authCredentialType = config.getAuthCredentialType?.(config.model.provider);
4525
4525
 
4526
- const resolvedMetadata = config.metadataResolver ? config.metadataResolver(config.model.provider) : config.metadata;
4526
+ const resolvedMetadata = config.metadataResolver
4527
+ ? config.metadataResolver({
4528
+ provider: config.model.provider,
4529
+ model: config.model,
4530
+ transport: streamFunction === streamSimple ? "default" : "custom",
4531
+ })
4532
+ : config.metadata;
4527
4533
 
4528
4534
  // Synthetic recovery requests choose their tool mode explicitly below and
4529
4535
  // must never consume a queued dynamic choice intended for an ordinary turn.
package/src/agent.ts CHANGED
@@ -38,6 +38,7 @@ import type {
38
38
  AgentEvent,
39
39
  AgentLoopConfig,
40
40
  AgentMessage,
41
+ AgentMetadataResolverContext,
41
42
  AgentState,
42
43
  AgentTool,
43
44
  AgentToolContext,
@@ -466,7 +467,7 @@ export class Agent {
466
467
  #sessionId?: string;
467
468
  #providerSessionId?: string;
468
469
  #metadata?: Record<string, unknown>;
469
- #metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
470
+ #metadataResolver?: (context: AgentMetadataResolverContext) => Record<string, unknown> | undefined;
470
471
  #providerSessionState?: Map<string, ProviderSessionState>;
471
472
  #thinkingBudgets?: ThinkingBudgets;
472
473
  #temperature?: number;
@@ -505,6 +506,8 @@ export class Agent {
505
506
  #onHarmonyLeak?: (event: HarmonyAuditEvent) => void | Promise<void>;
506
507
  #onBeforeYield?: () => Promise<void> | void;
507
508
  #shouldPause?: AgentLoopConfig["shouldPause"];
509
+ /** While set and returning true, steering is neither admitted nor dequeued. */
510
+ #steeringAdmissionFence?: () => boolean;
508
511
  #maintainContext?: AgentLoopConfig["maintainContext"];
509
512
  #telemetry?: AgentLoopConfig["telemetry"];
510
513
  #appendOnlyContext?: AppendOnlyContextManager;
@@ -669,8 +672,12 @@ export class Agent {
669
672
  * only included for `"anthropic"` requests). Falls back to the static
670
673
  * {@link metadata} value when no resolver is set.
671
674
  */
672
- metadataForProvider(provider: string): Record<string, unknown> | undefined {
673
- if (this.#metadataResolver) return this.#metadataResolver(provider);
675
+ metadataForProvider(
676
+ provider: string,
677
+ model?: Model,
678
+ transport?: AgentMetadataResolverContext["transport"],
679
+ ): Record<string, unknown> | undefined {
680
+ if (this.#metadataResolver) return this.#metadataResolver({ provider, model, transport });
674
681
  return this.#metadata;
675
682
  }
676
683
 
@@ -682,7 +689,9 @@ export class Agent {
682
689
  * credential. Pass `undefined` to clear and revert to the static
683
690
  * {@link metadata} value.
684
691
  */
685
- setMetadataResolver(resolver: ((provider: string) => Record<string, unknown> | undefined) | undefined): void {
692
+ setMetadataResolver(
693
+ resolver: ((context: AgentMetadataResolverContext) => Record<string, unknown> | undefined) | undefined,
694
+ ): void {
686
695
  this.#metadataResolver = resolver;
687
696
  }
688
697
 
@@ -891,6 +900,24 @@ export class Agent {
891
900
  this.#shouldPause = fn;
892
901
  }
893
902
 
903
+ /** The currently installed cooperative pause checkpoint, if any. */
904
+ get shouldPause(): AgentLoopConfig["shouldPause"] | undefined {
905
+ return this.#shouldPause;
906
+ }
907
+
908
+ /**
909
+ * Fence old-turn steering admission.
910
+ *
911
+ * The loop polls steering UPSTREAM of its pause checkpoint (and again on the
912
+ * immediate-interrupt path), so a cooperative stop alone cannot prevent one
913
+ * more old-turn model call once a steering message has already been dequeued.
914
+ * While the fence returns true the poll yields no messages AND does not
915
+ * dequeue, so the queue survives intact for the next turn.
916
+ */
917
+ setSteeringAdmissionFence(fn: (() => boolean) | undefined): void {
918
+ this.#steeringAdmissionFence = fn;
919
+ }
920
+
894
921
  setMaintainContext(fn: AgentLoopConfig["maintainContext"] | undefined): void {
895
922
  this.#maintainContext = fn;
896
923
  }
@@ -1911,6 +1938,12 @@ export class Agent {
1911
1938
  skipInitialSteeringPoll = false;
1912
1939
  return [];
1913
1940
  }
1941
+ // Fenced: yield nothing and dequeue nothing, so a steer submitted while a
1942
+ // fold is being claimed is neither consumed by the run being wound down
1943
+ // nor lost.
1944
+ if (this.#steeringAdmissionFence?.() === true) {
1945
+ return [];
1946
+ }
1914
1947
  const queued = this.#dequeueSteeringMessages();
1915
1948
  if (this.#activeRunId !== runId) {
1916
1949
  this.#steeringQueue = [...queued, ...this.#steeringQueue];
package/src/types.ts CHANGED
@@ -28,6 +28,21 @@ export type StreamFn = (
28
28
  ...args: Parameters<typeof streamSimple>
29
29
  ) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
30
30
 
31
+ /**
32
+ * Request context supplied to provider-aware metadata resolvers.
33
+ *
34
+ * The model is the exact model selected for the concrete request (including
35
+ * fallback and ephemeral requests), while `transport` distinguishes the
36
+ * built-in stream path from a caller-supplied stream function. Metadata that
37
+ * carries provider identity must use both values to fail closed when routing
38
+ * is not the canonical provider path.
39
+ */
40
+ export interface AgentMetadataResolverContext {
41
+ provider: string;
42
+ model?: Model;
43
+ transport?: "default" | "custom";
44
+ }
45
+
31
46
  /** Stable identifier for a managed logical run, shared by all of its retry attempts. */
32
47
  export type ManagedLogicalRunId = number;
33
48
  /** A resource owned by a prompt run until its promise settles. */
@@ -257,7 +272,7 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
257
272
  * current when `AgentLoopConfig` was first constructed). Overrides the static
258
273
  * `metadata` field when present.
259
274
  */
260
- metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
275
+ metadataResolver?: (context: AgentMetadataResolverContext) => Record<string, unknown> | undefined;
261
276
 
262
277
  /**
263
278
  * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.