@rowan-agent/agent 0.20.1 → 0.21.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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _rowan_agent_models from '@rowan-agent/models';
2
- import { ContentBlock, LlmContentPart, ModelRef, ScopeRef, ToolCallStatus, JsonValue as JsonValue$1, ToolProgress, ToolCallContent, ToolCallLocation, JsonObject as JsonObject$1, ToolKind, ModelConfig, StreamFn, ToolAnnotations, ThinkingLevel, ToolCall as ToolCall$1, ToolCallUpdate } from '@rowan-agent/models';
2
+ import { ContentBlock, ModelRef, LlmTokenUsage, LlmContentPart, ScopeRef, ToolCallStatus, JsonValue as JsonValue$1, ToolProgress, ToolCallContent, ToolCallLocation, JsonObject as JsonObject$1, ToolKind, ModelConfig, StreamFn, ToolAnnotations, ThinkingLevel, ToolCall as ToolCall$1, ToolCallUpdate } from '@rowan-agent/models';
3
3
  export { ModelConfig, ModelRef, ProviderCallContext, ProviderConfig, ProviderModelConfig, ProviderStreamFn, ScopeRef, StreamFn, ThinkingLevel, ToolAnnotations, ToolCall, ToolCallContent, ToolCallLocation, ToolCallOptions, ToolCallOutcome, ToolCallStatus, ToolCallUpdate, ToolDefinitionSummary, ToolKind, ToolProgress } from '@rowan-agent/models';
4
4
  import Type from 'typebox';
5
5
 
@@ -8,6 +8,8 @@ type ModelInvokeOutput = {
8
8
  contentBlocks: ContentBlock[];
9
9
  toolCalls: ToolCall[];
10
10
  stopReason?: string;
11
+ model?: ModelRef;
12
+ usage?: LlmTokenUsage;
11
13
  };
12
14
  /** Snapshot of phase-level context for restore */
13
15
  type PhaseContextSnapshot = {
@@ -27,7 +29,10 @@ type PhaseExecution = {
27
29
  }): Promise<ModelInvokeOutput>;
28
30
  reportStatus(status: PhaseStatus): Promise<void>;
29
31
  executeTool(context: AgentContext, toolCall: ToolCall): Promise<ToolResult>;
30
- executeTools(context: AgentContext, toolCalls: readonly ToolCall[], contentBlocks?: readonly ContentBlock[]): Promise<readonly ToolResult[]>;
32
+ executeTools(context: AgentContext, toolCalls: readonly ToolCall[], contentBlocks?: readonly ContentBlock[], meta?: {
33
+ model?: ModelRef;
34
+ usage?: LlmTokenUsage;
35
+ }): Promise<readonly ToolResult[]>;
31
36
  interaction: RunInteractionDriver;
32
37
  /** Message lifecycle manager for streaming updates from programmatic phases */
33
38
  messages: PhaseMessageManager;
@@ -45,7 +50,10 @@ type PhaseMessageManager = {
45
50
  /** Replace the active message content with model-native content parts */
46
51
  replaceContent(messageId: string, content: string | LlmContentPart[]): void;
47
52
  /** End the message stream, appends to transcript */
48
- end(messageId: string): Promise<void>;
53
+ end(messageId: string, meta?: {
54
+ model?: ModelRef;
55
+ usage?: LlmTokenUsage;
56
+ }): Promise<void>;
49
57
  /** Drop an unstarted stream that produced no content. */
50
58
  discard(messageId: string): void;
51
59
  };
@@ -1078,6 +1086,8 @@ interface OwnedStore {
1078
1086
  locations?: ToolCallLocation[];
1079
1087
  content?: ToolCallContent[];
1080
1088
  _meta?: JsonObject;
1089
+ model?: ModelRef;
1090
+ usage?: LlmTokenUsage;
1081
1091
  }): Promise<ToolCommit>;
1082
1092
  reserveToolCalls(input: {
1083
1093
  runId: RunId;
@@ -1087,6 +1097,8 @@ interface OwnedStore {
1087
1097
  calls: readonly ToolCallReservation[];
1088
1098
  /** The model response's blocks, so the request message keeps what preceded the Tool Calls. */
1089
1099
  contentBlocks?: readonly ContentBlock[];
1100
+ model?: ModelRef;
1101
+ usage?: LlmTokenUsage;
1090
1102
  }): Promise<ToolBatchCommit>;
1091
1103
  startToolCall(input: {
1092
1104
  runId: RunId;
@@ -1163,6 +1175,9 @@ type AgentRuntimeOptions = Readonly<{
1163
1175
  concurrency?: number;
1164
1176
  bootstrap?: (registry: RuntimeBootstrapRegistry) => void | Promise<void>;
1165
1177
  host?: ExtensionHost;
1178
+ sessionFiles?: Readonly<{
1179
+ dir: string;
1180
+ }>;
1166
1181
  /** @internal Private / testing parameter to override retry delay */
1167
1182
  retryDelayMs?: number;
1168
1183
  }>;
@@ -2088,6 +2103,8 @@ type AssistantMessage = MessageBase & Readonly<{
2088
2103
  role: "assistant";
2089
2104
  content: AssistantContent;
2090
2105
  interrupted?: boolean;
2106
+ model?: ModelRef;
2107
+ usage?: LlmTokenUsage;
2091
2108
  }>;
2092
2109
  type ToolMessage = MessageBase & Readonly<{
2093
2110
  role: "tool";
@@ -2343,6 +2360,8 @@ type AgentMessage = {
2343
2360
  phase?: string;
2344
2361
  };
2345
2362
  contexts?: readonly ContextCandidate[];
2363
+ model?: ModelRef;
2364
+ usage?: LlmTokenUsage;
2346
2365
  };
2347
2366
  type Skill = {
2348
2367
  name: string;
@@ -2754,6 +2773,8 @@ declare class InMemoryStore implements DurableStore {
2754
2773
  args: JsonValue;
2755
2774
  toolCallId?: ToolCallId;
2756
2775
  providerToolCallId?: string;
2776
+ model?: ModelRef;
2777
+ usage?: LlmTokenUsage;
2757
2778
  }): ToolCommit;
2758
2779
  reserveToolCalls(lease: OwnerLease, input: {
2759
2780
  runId: RunId;
@@ -2763,6 +2784,8 @@ declare class InMemoryStore implements DurableStore {
2763
2784
  calls: readonly ToolCallReservation[];
2764
2785
  /** The model response's blocks, so the request message keeps what preceded the Tool Calls. */
2765
2786
  contentBlocks?: readonly ContentBlock[];
2787
+ model?: ModelRef;
2788
+ usage?: LlmTokenUsage;
2766
2789
  }): ToolBatchCommit;
2767
2790
  startToolCall(lease: OwnerLease, input: {
2768
2791
  runId: RunId;
@@ -3018,6 +3041,8 @@ declare class SqliteStore implements DurableStore {
3018
3041
  locations?: ToolCallLocation[];
3019
3042
  content?: ToolCallContent[];
3020
3043
  _meta?: JsonObject;
3044
+ model?: _rowan_agent_models.ModelRef;
3045
+ usage?: _rowan_agent_models.LlmTokenUsage;
3021
3046
  }): Promise<ToolCommit>;
3022
3047
  reserveToolCalls(lease: OwnerLease, input: {
3023
3048
  runId: RunId;
@@ -3026,6 +3051,8 @@ declare class SqliteStore implements DurableStore {
3026
3051
  requestMessageId: MessageId;
3027
3052
  calls: readonly ToolCallReservation[];
3028
3053
  contentBlocks?: readonly ContentBlock[];
3054
+ model?: _rowan_agent_models.ModelRef;
3055
+ usage?: _rowan_agent_models.LlmTokenUsage;
3029
3056
  }): Promise<ToolBatchCommit>;
3030
3057
  startToolCall(lease: OwnerLease, input: {
3031
3058
  runId: RunId;
package/dist/index.js CHANGED
@@ -944,7 +944,7 @@ function createRuntimeCoreTools(input = {}) {
944
944
  // src/runtime/durable-runtime.ts
945
945
  import { mkdir as mkdir2, chmod as chmod2, writeFile as writeFile2 } from "fs/promises";
946
946
  import { randomUUID } from "crypto";
947
- import { join as join5 } from "path";
947
+ import { join as join6 } from "path";
948
948
  import { createModelStream, isValidToolProgress } from "@rowan-agent/models";
949
949
 
950
950
  // src/utils.ts
@@ -1378,46 +1378,11 @@ var LoopGuard = {
1378
1378
  // src/loop/stream-collector.ts
1379
1379
  var MAX_STREAMED_OUTPUT_CHARACTERS = 1024 * 1024;
1380
1380
  async function invokeModel(input) {
1381
- const baseContext = input.callContext ?? {
1382
- signal: input.config.signal ?? new AbortController().signal,
1383
- run: {
1384
- id: input.config.execution.runId,
1385
- agentId: input.config.execution.agentId,
1386
- scope: input.config.execution.scope ?? [],
1387
- cwd: input.config.execution.cwd ?? input.config.context.cwd ?? ""
1388
- },
1389
- interact: async (request) => {
1390
- const handler = input.config.runtime?.interact ?? input.config.interact;
1391
- if (!handler) {
1392
- throw new Error("Run interactions are not supported in this runtime.");
1393
- }
1394
- return handler(request);
1395
- },
1396
- tools: {
1397
- list: () => {
1398
- return input.config.context.tools.map((t) => ({
1399
- name: t.name,
1400
- description: t.description,
1401
- parameters: t.parameters,
1402
- ...t._meta !== void 0 ? { _meta: t._meta } : {}
1403
- }));
1404
- },
1405
- call: async (_name, _args, _options) => {
1406
- throw new Error("Tool calling is not available in standalone stream collector without callContext.");
1407
- },
1408
- report: (update) => {
1409
- input.config.onToolReport?.({
1410
- runId: input.config.execution.runId,
1411
- update
1412
- });
1413
- }
1414
- }
1415
- };
1416
1381
  const position = { contentIndex: 0 };
1417
1382
  const callContext = {
1418
- ...baseContext,
1383
+ ...input.callContext,
1419
1384
  tools: {
1420
- ...baseContext.tools,
1385
+ ...input.callContext.tools,
1421
1386
  report: (update) => {
1422
1387
  input.config.onToolReport?.({
1423
1388
  runId: input.config.execution.runId,
@@ -1436,7 +1401,8 @@ async function invokeModel(input) {
1436
1401
  metadataPhase: input.phaseId,
1437
1402
  output: input.output,
1438
1403
  onThinkingDelta: input.onThinkingDelta,
1439
- onToolCallDelta: input.onToolCallDelta
1404
+ onToolCallDelta: input.onToolCallDelta,
1405
+ model: input.request.model
1440
1406
  });
1441
1407
  const response = result.doneResponse ?? {
1442
1408
  content: result.text,
@@ -1452,6 +1418,8 @@ async function invokeModel(input) {
1452
1418
  contentBlocks: result.contentBlocks,
1453
1419
  toolCalls: result.toolCalls,
1454
1420
  stopReason: result.stopReason,
1421
+ model: input.request.model,
1422
+ usage: response.usage,
1455
1423
  transcript: {
1456
1424
  request: input.request,
1457
1425
  response
@@ -1575,7 +1543,7 @@ async function collectStreamResult(input) {
1575
1543
  input.message.replaceContent(activeMessageId, contentBlocksToMessageContent(contentBlocks2));
1576
1544
  }
1577
1545
  if (persistAssistant && activeMessageId) {
1578
- if (hasContent2) await input.message.end(activeMessageId);
1546
+ if (hasContent2) await input.message.end(activeMessageId, { model: input.model, usage: event.response?.usage });
1579
1547
  else input.message.discard(activeMessageId);
1580
1548
  activeMessageId = void 0;
1581
1549
  }
@@ -1584,13 +1552,13 @@ async function collectStreamResult(input) {
1584
1552
  const abortResult = LoopGuard.checkAbort(input.config.signal);
1585
1553
  if (abortResult.stopReason !== "none") {
1586
1554
  if (persistAssistant && activeMessageId) {
1587
- await input.message.end(activeMessageId);
1555
+ await input.message.end(activeMessageId, { model: input.model });
1588
1556
  activeMessageId = void 0;
1589
1557
  }
1590
1558
  return { text: abortResult.message, contentBlocks: [], toolCalls: [], stopReason: "aborted" };
1591
1559
  }
1592
1560
  if (persistAssistant && activeMessageId) {
1593
- await input.message.end(activeMessageId);
1561
+ await input.message.end(activeMessageId, { model: input.model, usage: doneResponse?.usage });
1594
1562
  }
1595
1563
  const hasContent = (lastPartial?.contentBlocks?.length ?? 0) > 0 || (doneResponse?.content?.length ?? 0) > 0 || (doneResponse?.toolCalls?.length ?? 0) > 0;
1596
1564
  if (!hasContent && stopReason !== "error" && stopReason !== "aborted") {
@@ -2741,6 +2709,7 @@ function buildModelRequest(input, options) {
2741
2709
  model: options?.model ?? { provider: "", id: "" },
2742
2710
  ...options?.thinkingLevel === void 0 ? {} : { thinkingLevel: options.thinkingLevel },
2743
2711
  ...options?.providerOptions === void 0 ? {} : { providerOptions: options.providerOptions },
2712
+ ...options?.sessionId === void 0 ? {} : { sessionId: options.sessionId },
2744
2713
  system: systemText,
2745
2714
  messages,
2746
2715
  tools: modelTools.length > 0 ? modelTools : void 0
@@ -3006,9 +2975,15 @@ function createMessageManager(context, onMessage, onMessageDelta) {
3006
2975
  if (!active) return;
3007
2976
  active.message.content = content;
3008
2977
  },
3009
- async end(messageId) {
2978
+ async end(messageId, meta) {
3010
2979
  const active = activeMessages.get(messageId);
3011
2980
  if (!active) return;
2981
+ if (meta?.model) {
2982
+ active.message.model = meta.model;
2983
+ }
2984
+ if (meta?.usage) {
2985
+ active.message.usage = meta.usage;
2986
+ }
3012
2987
  if (!active.emitted && (active.message.content === "" || active.message.content.length === 0)) {
3013
2988
  activeMessages.delete(messageId);
3014
2989
  return;
@@ -3077,6 +3052,7 @@ async function executePhaseWithModel(ctx) {
3077
3052
  };
3078
3053
  let executableToolCalls;
3079
3054
  let roundContentBlocks;
3055
+ let collected;
3080
3056
  const checkpoint = ctx.execution.interaction.checkpoint();
3081
3057
  if (checkpoint && typeof checkpoint === "object" && checkpoint.kind === "tool_call_suspension") {
3082
3058
  const toolSuspension = checkpoint;
@@ -3089,7 +3065,7 @@ async function executePhaseWithModel(ctx) {
3089
3065
  toolCalls: [...toolSuspension.toolCalls]
3090
3066
  };
3091
3067
  } else {
3092
- const collected = await ctx.execution.invokeModel(roundContext);
3068
+ collected = await ctx.execution.invokeModel(roundContext);
3093
3069
  output = {
3094
3070
  message: collected.text,
3095
3071
  phase: ctx.phase.name,
@@ -3122,7 +3098,13 @@ async function executePhaseWithModel(ctx) {
3122
3098
  return output;
3123
3099
  }
3124
3100
  }
3125
- const results = await ctx.execution.executeTools(roundContext, executableToolCalls, roundContentBlocks);
3101
+ const currentModel = ctx.phase.model ?? ctx.config.model;
3102
+ const results = await ctx.execution.executeTools(
3103
+ roundContext,
3104
+ executableToolCalls,
3105
+ roundContentBlocks,
3106
+ { model: currentModel, usage: collected?.usage }
3107
+ );
3126
3108
  for (const result of results) {
3127
3109
  const messageId = ctx.messageManager.start("tool", createToolResultContent(result), {
3128
3110
  phase: ctx.phase.name
@@ -3676,7 +3658,9 @@ async function executeToolCalls(input) {
3676
3658
  config: input.config,
3677
3659
  toolCalls: input.toolCalls,
3678
3660
  contentBlocks: input.contentBlocks,
3679
- driver: input.driver
3661
+ driver: input.driver,
3662
+ model: input.model,
3663
+ usage: input.usage
3680
3664
  });
3681
3665
  if (results2.length !== input.toolCalls.length) throw new Error("Runtime returned an invalid Tool batch result");
3682
3666
  return results2.map((result, index) => ({
@@ -3748,6 +3732,7 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3748
3732
  appendSystemPrompt: phaseContext.appendSystemPrompt
3749
3733
  }, {
3750
3734
  model: phase.model ?? config.model,
3735
+ sessionId: config.execution.agentId,
3751
3736
  ...config.thinkingLevel === void 0 ? {} : { thinkingLevel: config.thinkingLevel },
3752
3737
  ...config.providerOptions === void 0 ? {} : { providerOptions: config.providerOptions }
3753
3738
  });
@@ -3796,12 +3781,6 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3796
3781
  };
3797
3782
  const res = await executePhaseTool(phaseContext, toolCall, options2, true);
3798
3783
  return res.ok ? { ok: true, content: res.content } : { ok: false, error: res.error ?? "Tool execution failed", ...res.content !== void 0 ? { content: res.content } : {} };
3799
- },
3800
- report: (update) => {
3801
- config.onToolReport?.({
3802
- runId: config.execution.runId,
3803
- update
3804
- });
3805
3784
  }
3806
3785
  }
3807
3786
  };
@@ -3839,7 +3818,7 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3839
3818
  await config.onPhaseStatus?.(phase.name, status);
3840
3819
  },
3841
3820
  executeTool: executePhaseTool,
3842
- async executeTools(phaseContext, toolCalls, contentBlocks) {
3821
+ async executeTools(phaseContext, toolCalls, contentBlocks, meta) {
3843
3822
  return runTurn(async () => {
3844
3823
  const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
3845
3824
  for (const toolCall of toolCalls) {
@@ -3857,7 +3836,9 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3857
3836
  tools,
3858
3837
  toolCalls,
3859
3838
  contentBlocks,
3860
- driver: interaction
3839
+ driver: interaction,
3840
+ model: meta?.model,
3841
+ usage: meta?.usage
3861
3842
  });
3862
3843
  for (const result of results) {
3863
3844
  await toolExecutionManager.end(result.toolCallId, result.toolName, result, !result.ok);
@@ -4324,7 +4305,7 @@ function normalizeUserInput(input) {
4324
4305
  return normalized;
4325
4306
  }
4326
4307
  function isAssistantMessage(value) {
4327
- return isRecord3(value) && hasOnlyKeys(value, ["id", "agentId", "runId", "messageRevision", "role", "content", "metadata", "sequenceWithinRun", "createdAt", "interrupted"]) && typeof value.id === "string" && typeof value.agentId === "string" && typeof value.runId === "string" && value.role === "assistant" && (value.messageRevision === void 0 || Number.isInteger(value.messageRevision) && value.messageRevision >= 0) && Number.isInteger(value.sequenceWithinRun) && value.sequenceWithinRun >= 0 && typeof value.createdAt === "string" && isAssistantContent(value.content) && (value.metadata === void 0 || isMetadata(value.metadata)) && (value.interrupted === void 0 || typeof value.interrupted === "boolean");
4308
+ return isRecord3(value) && hasOnlyKeys(value, ["id", "agentId", "runId", "messageRevision", "role", "content", "metadata", "sequenceWithinRun", "createdAt", "interrupted", "model", "usage"]) && typeof value.id === "string" && typeof value.agentId === "string" && typeof value.runId === "string" && value.role === "assistant" && (value.messageRevision === void 0 || Number.isInteger(value.messageRevision) && value.messageRevision >= 0) && Number.isInteger(value.sequenceWithinRun) && value.sequenceWithinRun >= 0 && typeof value.createdAt === "string" && isAssistantContent(value.content) && (value.metadata === void 0 || isMetadata(value.metadata)) && (value.interrupted === void 0 || typeof value.interrupted === "boolean") && (value.model === void 0 || isRecord3(value.model) && typeof value.model.provider === "string" && typeof value.model.id === "string") && (value.usage === void 0 || isRecord3(value.usage));
4328
4309
  }
4329
4310
  function isToolResult(value) {
4330
4311
  if (!isRecord3(value)) return false;
@@ -5040,7 +5021,9 @@ function projectAssistantMessage(message, agentId, runId, sequenceWithinRun, opt
5040
5021
  sequenceWithinRun,
5041
5022
  createdAt: message.createdAt,
5042
5023
  ...message.metadata ? { metadata: message.metadata } : {},
5043
- ...options.interrupted ? { interrupted: true } : {}
5024
+ ...options.interrupted ? { interrupted: true } : {},
5025
+ ...message.model ? { model: message.model } : {},
5026
+ ...message.usage ? { usage: message.usage } : {}
5044
5027
  };
5045
5028
  }
5046
5029
  function durableInterruptedAssistantContent(content) {
@@ -5292,6 +5275,333 @@ function fromLoopResult(result) {
5292
5275
  };
5293
5276
  }
5294
5277
 
5278
+ // src/runtime/session-files.ts
5279
+ import { appendFileSync, chmodSync, existsSync as existsSync3, mkdirSync, readFileSync } from "fs";
5280
+ import { dirname as dirname5, join as join3 } from "path";
5281
+ function toEpochMs(isoString) {
5282
+ if (!isoString) return Date.now();
5283
+ const ms = new Date(isoString).getTime();
5284
+ return Number.isNaN(ms) ? Date.now() : ms;
5285
+ }
5286
+ function isContentBlock(value) {
5287
+ if (!value || typeof value !== "object") return false;
5288
+ const candidate = value;
5289
+ if (candidate.type === "text" && typeof candidate.text === "string") return true;
5290
+ if (candidate.type === "image" && typeof candidate.data === "string" && typeof candidate.mimeType === "string") return true;
5291
+ return false;
5292
+ }
5293
+ function convertToolResultContent(content) {
5294
+ if (content === void 0 || content === null) {
5295
+ return [{ type: "text", text: "" }];
5296
+ }
5297
+ if (typeof content === "string") {
5298
+ return [{ type: "text", text: content }];
5299
+ }
5300
+ if (isContentBlock(content)) {
5301
+ return content.type === "image" ? [{ type: "image", data: content.data, mimeType: content.mimeType }] : [{ type: "text", text: content.text }];
5302
+ }
5303
+ if (Array.isArray(content)) {
5304
+ if (content.length > 0 && content.every(isContentBlock)) {
5305
+ return content.map(
5306
+ (item) => item.type === "image" ? { type: "image", data: item.data, mimeType: item.mimeType } : { type: "text", text: item.text }
5307
+ );
5308
+ }
5309
+ return [{ type: "text", text: JSON.stringify(content) }];
5310
+ }
5311
+ return [{ type: "text", text: JSON.stringify(content) }];
5312
+ }
5313
+ function convertUserContent(content) {
5314
+ if (typeof content === "string") return content;
5315
+ return content.map(
5316
+ (part) => part.type === "image" ? { type: "image", data: part.data, mimeType: part.mimeType } : { type: "text", text: part.text }
5317
+ );
5318
+ }
5319
+ function convertAssistantContent(content, onToolCall) {
5320
+ if (typeof content === "string") {
5321
+ return {
5322
+ parts: [{ type: "text", text: content }],
5323
+ hasToolCall: false
5324
+ };
5325
+ }
5326
+ let hasToolCall = false;
5327
+ const parts = [];
5328
+ for (const part of content) {
5329
+ if (part.type === "text") {
5330
+ parts.push({ type: "text", text: part.text });
5331
+ } else if (part.type === "thinking") {
5332
+ parts.push({
5333
+ type: "thinking",
5334
+ thinking: part.thinking,
5335
+ ...part.signature ? { thinkingSignature: part.signature } : {}
5336
+ });
5337
+ } else if (part.type === "tool_use") {
5338
+ hasToolCall = true;
5339
+ const callId = part.providerToolCallId ?? part.toolCallId;
5340
+ onToolCall?.(callId, part.name, part.toolCallId);
5341
+ parts.push({
5342
+ type: "toolCall",
5343
+ id: callId,
5344
+ name: part.name,
5345
+ arguments: part.input
5346
+ });
5347
+ }
5348
+ }
5349
+ return { parts, hasToolCall };
5350
+ }
5351
+ async function getAgentCreatedAt(runtime, agentId) {
5352
+ let after;
5353
+ while (true) {
5354
+ const page = await runtime.listAgents(after ? { after, limit: 100 } : { limit: 100 });
5355
+ const agent = page.items.find((item) => item.id === agentId);
5356
+ if (agent) return agent.createdAt;
5357
+ if (!page.next) break;
5358
+ after = page.next;
5359
+ }
5360
+ return (/* @__PURE__ */ new Date()).toISOString();
5361
+ }
5362
+ async function getFirstRunCwd(runtime, agentId) {
5363
+ let after;
5364
+ while (true) {
5365
+ const page = await runtime.listRuns(after ? { agentId, after, limit: 100 } : { agentId, limit: 100 });
5366
+ for (const runSummary of page.items) {
5367
+ try {
5368
+ const snapshot = await runtime.run(runSummary.id).snapshot();
5369
+ if (snapshot.cwd && snapshot.cwd.trim().length > 0) {
5370
+ return snapshot.cwd;
5371
+ }
5372
+ } catch {
5373
+ }
5374
+ }
5375
+ if (!page.next) break;
5376
+ after = page.next;
5377
+ }
5378
+ return "";
5379
+ }
5380
+ function writeSessionEntry(state, entry) {
5381
+ appendFileSync(state.filePath, `${JSON.stringify(entry)}
5382
+ `, { encoding: "utf8", mode: 384 });
5383
+ try {
5384
+ chmodSync(state.filePath, 384);
5385
+ } catch {
5386
+ }
5387
+ state.knownEntryIds.add(entry.id);
5388
+ state.entryIdToParentId.set(entry.id, entry.parentId);
5389
+ state.lastEntryId = entry.id;
5390
+ }
5391
+ async function startSessionFilesConsumer(runtime, options) {
5392
+ const agentStates = /* @__PURE__ */ new Map();
5393
+ async function getOrInitState(agentId) {
5394
+ let state = agentStates.get(agentId);
5395
+ if (state) return state;
5396
+ const createdAt = await getAgentCreatedAt(runtime, agentId);
5397
+ const cwd = await getFirstRunCwd(runtime, agentId);
5398
+ const folderName = cwd.length > 0 ? `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--` : "--unknown--";
5399
+ const fileTimestamp = createdAt.replace(/[:.]/g, "-");
5400
+ const fileName = `${fileTimestamp}_${agentId}.jsonl`;
5401
+ const filePath = join3(options.dir, folderName, fileName);
5402
+ const knownEntryIds = /* @__PURE__ */ new Set();
5403
+ let lastEntryId = null;
5404
+ let lastModel;
5405
+ const entryIdToParentId = /* @__PURE__ */ new Map();
5406
+ const toolCallIdToName = /* @__PURE__ */ new Map();
5407
+ const writeHeader = () => {
5408
+ const header = { type: "session", version: 3, id: agentId, timestamp: createdAt, cwd };
5409
+ appendFileSync(filePath, `${JSON.stringify(header)}
5410
+ `, { encoding: "utf8", mode: 384 });
5411
+ };
5412
+ mkdirSync(dirname5(filePath), { recursive: true, mode: 448 });
5413
+ if (existsSync3(filePath)) {
5414
+ const content = readFileSync(filePath, "utf8");
5415
+ if (content.trim().length === 0) {
5416
+ writeHeader();
5417
+ } else {
5418
+ for (const line of content.split("\n")) {
5419
+ const trimmed = line.trim();
5420
+ if (!trimmed) continue;
5421
+ let parsed;
5422
+ try {
5423
+ parsed = JSON.parse(trimmed);
5424
+ } catch {
5425
+ continue;
5426
+ }
5427
+ if (parsed.type === "session") continue;
5428
+ if (typeof parsed.id === "string") {
5429
+ knownEntryIds.add(parsed.id);
5430
+ entryIdToParentId.set(parsed.id, parsed.parentId ?? null);
5431
+ lastEntryId = parsed.id;
5432
+ }
5433
+ if (parsed.type === "model_change" && typeof parsed.provider === "string" && typeof parsed.modelId === "string") {
5434
+ lastModel = { provider: parsed.provider, modelId: parsed.modelId };
5435
+ }
5436
+ if (parsed.type === "message" && parsed.message?.role === "assistant" && Array.isArray(parsed.message.content)) {
5437
+ for (const part of parsed.message.content) {
5438
+ if (part && part.type === "toolCall" && typeof part.id === "string" && typeof part.name === "string") {
5439
+ toolCallIdToName.set(part.id, part.name);
5440
+ }
5441
+ }
5442
+ }
5443
+ }
5444
+ }
5445
+ } else {
5446
+ writeHeader();
5447
+ }
5448
+ state = {
5449
+ filePath,
5450
+ knownEntryIds,
5451
+ lastEntryId,
5452
+ entryIdToParentId,
5453
+ toolCallIdToName,
5454
+ ...lastModel ? { lastModel } : {}
5455
+ };
5456
+ agentStates.set(agentId, state);
5457
+ return state;
5458
+ }
5459
+ async function onEvent(event) {
5460
+ try {
5461
+ if (event.kind !== "message_committed" && event.kind !== "message_revised") {
5462
+ return;
5463
+ }
5464
+ if (event.kind === "message_committed" && event.message.role === "interaction") {
5465
+ return;
5466
+ }
5467
+ const state = await getOrInitState(event.agentId);
5468
+ if (event.kind === "message_committed") {
5469
+ if (event.message.role === "user") {
5470
+ if (state.knownEntryIds.has(event.message.id)) return;
5471
+ const content = convertUserContent(event.message.content);
5472
+ const entry = {
5473
+ type: "message",
5474
+ id: event.message.id,
5475
+ parentId: state.lastEntryId,
5476
+ timestamp: event.message.createdAt,
5477
+ message: {
5478
+ role: "user",
5479
+ content,
5480
+ timestamp: toEpochMs(event.message.createdAt)
5481
+ }
5482
+ };
5483
+ writeSessionEntry(state, entry);
5484
+ } else if (event.message.role === "assistant") {
5485
+ if (state.knownEntryIds.has(event.message.id)) return;
5486
+ const model = event.message.model;
5487
+ const rawUsage = event.message.usage;
5488
+ if (model) {
5489
+ const isDifferentModel = !state.lastModel || state.lastModel.provider !== model.provider || state.lastModel.modelId !== model.id;
5490
+ if (isDifferentModel) {
5491
+ const modelChangeId = `${event.message.id}:model`;
5492
+ if (!state.knownEntryIds.has(modelChangeId)) {
5493
+ const modelChangeEntry = {
5494
+ type: "model_change",
5495
+ id: modelChangeId,
5496
+ parentId: state.lastEntryId,
5497
+ timestamp: event.message.createdAt,
5498
+ provider: model.provider,
5499
+ modelId: model.id
5500
+ };
5501
+ writeSessionEntry(state, modelChangeEntry);
5502
+ }
5503
+ state.lastModel = { provider: model.provider, modelId: model.id };
5504
+ }
5505
+ }
5506
+ const { parts, hasToolCall } = convertAssistantContent(
5507
+ event.message.content,
5508
+ (id, name, internalId) => {
5509
+ state.toolCallIdToName.set(id, name);
5510
+ if (internalId) state.toolCallIdToName.set(internalId, name);
5511
+ }
5512
+ );
5513
+ const assistantMessagePayload = {
5514
+ role: "assistant",
5515
+ content: parts,
5516
+ stopReason: hasToolCall ? "toolUse" : "stop",
5517
+ timestamp: toEpochMs(event.message.createdAt)
5518
+ };
5519
+ if (model || rawUsage) {
5520
+ if (model) {
5521
+ assistantMessagePayload.provider = model.provider;
5522
+ assistantMessagePayload.model = model.id;
5523
+ }
5524
+ const input = rawUsage?.inputTokens ?? 0;
5525
+ const output = rawUsage?.outputTokens ?? 0;
5526
+ const cacheRead = rawUsage?.cacheReadTokens ?? 0;
5527
+ const cacheWrite = rawUsage?.cacheWriteTokens ?? 0;
5528
+ const totalTokens = rawUsage?.totalTokens ?? input + output + cacheRead + cacheWrite;
5529
+ const cost = rawUsage?.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
5530
+ assistantMessagePayload.usage = {
5531
+ input,
5532
+ output,
5533
+ cacheRead,
5534
+ cacheWrite,
5535
+ ...rawUsage?.cacheWrite1hTokens !== void 0 ? { cacheWrite1h: rawUsage.cacheWrite1hTokens } : {},
5536
+ ...rawUsage?.reasoningTokens !== void 0 ? { reasoning: rawUsage.reasoningTokens } : {},
5537
+ totalTokens,
5538
+ cost
5539
+ };
5540
+ }
5541
+ const entry = {
5542
+ type: "message",
5543
+ id: event.message.id,
5544
+ parentId: state.lastEntryId,
5545
+ timestamp: event.message.createdAt,
5546
+ message: assistantMessagePayload
5547
+ };
5548
+ writeSessionEntry(state, entry);
5549
+ } else if (event.message.role === "tool") {
5550
+ for (let index = 0; index < event.message.content.length; index++) {
5551
+ const part = event.message.content[index];
5552
+ const entryId = `${event.message.id}:${index}`;
5553
+ if (state.knownEntryIds.has(entryId)) continue;
5554
+ const toolCallId = part.providerToolCallId ?? part.toolCallId;
5555
+ const toolName = state.toolCallIdToName.get(toolCallId) ?? state.toolCallIdToName.get(part.toolCallId) ?? "unknown";
5556
+ const isError = Boolean(
5557
+ part.result.isError ?? (part.result.ok !== void 0 ? !part.result.ok : Boolean(part.result.error))
5558
+ );
5559
+ const content = convertToolResultContent(part.result.content);
5560
+ const entry = {
5561
+ type: "message",
5562
+ id: entryId,
5563
+ parentId: state.lastEntryId,
5564
+ timestamp: event.message.createdAt,
5565
+ message: {
5566
+ role: "toolResult",
5567
+ toolCallId,
5568
+ toolName,
5569
+ content,
5570
+ isError,
5571
+ timestamp: toEpochMs(event.message.createdAt)
5572
+ }
5573
+ };
5574
+ writeSessionEntry(state, entry);
5575
+ }
5576
+ }
5577
+ } else if (event.kind === "message_revised") {
5578
+ const message = event.message;
5579
+ const revision = message.messageRevision ?? 1;
5580
+ const entryId = `${message.id}@${revision}`;
5581
+ if (state.knownEntryIds.has(entryId)) return;
5582
+ const parentId = state.entryIdToParentId.get(message.id) ?? null;
5583
+ const timestampIso = message.createdAt ?? event.createdAt;
5584
+ const content = convertUserContent(message.content);
5585
+ const entry = {
5586
+ type: "message",
5587
+ id: entryId,
5588
+ parentId,
5589
+ timestamp: timestampIso,
5590
+ message: {
5591
+ role: "user",
5592
+ content,
5593
+ timestamp: toEpochMs(timestampIso)
5594
+ }
5595
+ };
5596
+ writeSessionEntry(state, entry);
5597
+ }
5598
+ } catch (error) {
5599
+ console.error("Failed to write session file entry:", error);
5600
+ }
5601
+ }
5602
+ return runtime.consume({ consumerId: "rowan:session-files", signal: new AbortController().signal, onEvent });
5603
+ }
5604
+
5295
5605
  // src/runtime/transient-run-events.ts
5296
5606
  var MAX_BUFFERED_EVENTS = 128;
5297
5607
  var MAX_BUFFERED_TEXT = 64 * 1024;
@@ -5648,7 +5958,7 @@ function scopeToKey(scope) {
5648
5958
 
5649
5959
  // src/extensions/runner.ts
5650
5960
  import { execFile } from "child_process";
5651
- import { dirname as dirname5, resolve as resolve5 } from "path";
5961
+ import { dirname as dirname6, resolve as resolve5 } from "path";
5652
5962
  import {
5653
5963
  registerModel,
5654
5964
  unregisterProviderModels,
@@ -6477,7 +6787,7 @@ var ExtensionRunner = class {
6477
6787
  }
6478
6788
  }
6479
6789
  async loadRegisteredPhase(extension, registration) {
6480
- const extensionBase = extension.path.startsWith("<") ? this.cwd : dirname5(extension.path);
6790
+ const extensionBase = extension.path.startsWith("<") ? this.cwd : dirname6(extension.path);
6481
6791
  const phase = await loadPhase(resolve5(extensionBase, registration));
6482
6792
  const name = phase.name;
6483
6793
  if (this.phases.has(name)) {
@@ -6553,10 +6863,10 @@ function createExtensionRunner(options) {
6553
6863
  }
6554
6864
 
6555
6865
  // src/extensions/loader.ts
6556
- import { existsSync as existsSync3, readFileSync } from "fs";
6866
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
6557
6867
  import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
6558
6868
  import { fileURLToPath } from "url";
6559
- import { basename as basename3, dirname as dirname6, extname as extname2, join as join3, resolve as resolve6 } from "path";
6869
+ import { basename as basename3, dirname as dirname7, extname as extname2, join as join4, resolve as resolve6 } from "path";
6560
6870
  import { createJiti } from "jiti";
6561
6871
  function isExtensionFile(path) {
6562
6872
  const extension = extname2(path).toLowerCase();
@@ -6566,12 +6876,12 @@ function isSyntheticPath(path) {
6566
6876
  return path.startsWith("<") && path.endsWith(">");
6567
6877
  }
6568
6878
  function readManifestSync(dir) {
6569
- const manifestPath = join3(dir, "package.json");
6570
- if (!existsSync3(manifestPath)) {
6879
+ const manifestPath = join4(dir, "package.json");
6880
+ if (!existsSync4(manifestPath)) {
6571
6881
  return void 0;
6572
6882
  }
6573
6883
  try {
6574
- const content = readFileSync(manifestPath, "utf8");
6884
+ const content = readFileSync2(manifestPath, "utf8");
6575
6885
  const pkg = JSON.parse(content);
6576
6886
  const rowan = pkg.rowan;
6577
6887
  if (!rowan) return void 0;
@@ -6590,7 +6900,7 @@ function jitiAliases() {
6590
6900
  return {};
6591
6901
  }
6592
6902
  const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
6593
- if (!existsSync3(agentSourcePath)) {
6903
+ if (!existsSync4(agentSourcePath)) {
6594
6904
  return {};
6595
6905
  }
6596
6906
  return { "@rowan-agent/agent": agentSourcePath };
@@ -6614,21 +6924,21 @@ async function loadExtensionModule(extensionPath) {
6614
6924
  return typeof module === "function" ? module : void 0;
6615
6925
  }
6616
6926
  async function readPackageManifest(path) {
6617
- if (!existsSync3(path)) {
6927
+ if (!existsSync4(path)) {
6618
6928
  return void 0;
6619
6929
  }
6620
6930
  const content = await readFile3(path, "utf8");
6621
6931
  return JSON.parse(content);
6622
6932
  }
6623
6933
  async function resolveExtensionEntries(dir) {
6624
- const manifest = await readPackageManifest(join3(dir, "package.json"));
6934
+ const manifest = await readPackageManifest(join4(dir, "package.json"));
6625
6935
  const declared = manifest?.rowan?.extensions;
6626
6936
  if (declared && declared.length > 0) {
6627
6937
  return declared.map((entry) => resolve6(dir, entry));
6628
6938
  }
6629
6939
  for (const name of ["index.ts", "index.js"]) {
6630
- const entry = join3(dir, name);
6631
- if (existsSync3(entry)) {
6940
+ const entry = join4(dir, name);
6941
+ if (existsSync4(entry)) {
6632
6942
  return [entry];
6633
6943
  }
6634
6944
  }
@@ -6643,7 +6953,7 @@ async function discoverExtensionsInDir(dir) {
6643
6953
  });
6644
6954
  const paths = [];
6645
6955
  for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
6646
- const entryPath = join3(dir, entry.name);
6956
+ const entryPath = join4(dir, entry.name);
6647
6957
  if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
6648
6958
  paths.push(entryPath);
6649
6959
  continue;
@@ -6663,8 +6973,8 @@ async function discoverExtensionsInDir(dir) {
6663
6973
  function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>", options = {}) {
6664
6974
  const resolvedCwd = resolve6(cwd);
6665
6975
  const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve6(resolvedCwd, extensionPath);
6666
- const name = options.name ?? (isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname6(resolvedPath)) || "unknown");
6667
- const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname6(resolvedPath);
6976
+ const name = options.name ?? (isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname7(resolvedPath)) || "unknown");
6977
+ const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname7(resolvedPath);
6668
6978
  const manifest = readManifestSync(manifestDir);
6669
6979
  const id = options.id ?? manifest?.id ?? name;
6670
6980
  return {
@@ -6704,7 +7014,7 @@ async function loadExtensionsFromPath(path) {
6704
7014
  const resolvedPath = resolve6(path);
6705
7015
  const info = await stat2(resolvedPath);
6706
7016
  if (!info.isDirectory()) {
6707
- return loadExtensions([resolvedPath], dirname6(resolvedPath));
7017
+ return loadExtensions([resolvedPath], dirname7(resolvedPath));
6708
7018
  }
6709
7019
  const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
6710
7020
  return loadExtensions(entries, resolvedPath);
@@ -6712,7 +7022,7 @@ async function loadExtensionsFromPath(path) {
6712
7022
 
6713
7023
  // src/runtime/resource-registry.ts
6714
7024
  import { stat as stat3, readdir as readdir4 } from "fs/promises";
6715
- import { join as join4, resolve as resolve7 } from "path";
7025
+ import { join as join5, resolve as resolve7 } from "path";
6716
7026
  var ResourceRegistryError = class extends Error {
6717
7027
  code;
6718
7028
  constructor(code, message) {
@@ -7030,7 +7340,7 @@ async function loadDirectoryValues(kind, directory, sourceId) {
7030
7340
  if (rootInfo.isFile()) {
7031
7341
  targets.push(root);
7032
7342
  } else if (rootInfo.isDirectory()) {
7033
- const markerAtRoot = join4(root, marker);
7343
+ const markerAtRoot = join5(root, marker);
7034
7344
  try {
7035
7345
  const markerInfo = await stat3(markerAtRoot);
7036
7346
  if (markerInfo.isFile()) targets.push(markerAtRoot);
@@ -7052,7 +7362,7 @@ async function loadDirectoryValues(kind, directory, sourceId) {
7052
7362
  }
7053
7363
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
7054
7364
  if (!entry.isDirectory()) continue;
7055
- const target = join4(root, entry.name, marker);
7365
+ const target = join5(root, entry.name, marker);
7056
7366
  try {
7057
7367
  const targetInfo = await stat3(target);
7058
7368
  if (targetInfo.isFile()) targets.push(target);
@@ -7308,7 +7618,7 @@ async function spillLargeToolResult(result, archiveDir, toolName) {
7308
7618
  await mkdir2(toolDir, { recursive: true, mode: 448 });
7309
7619
  await chmod2(toolDir, 448).catch(() => void 0);
7310
7620
  const safeToolName = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_");
7311
- const path = join5(toolDir, `rowan-${safeToolName}-${Date.now()}-${randomUUID()}.log`);
7621
+ const path = join6(toolDir, `rowan-${safeToolName}-${Date.now()}-${randomUUID()}.log`);
7312
7622
  await writeFile2(path, serialized, { encoding: "utf8", mode: 384 });
7313
7623
  await chmod2(path, 384).catch(() => void 0);
7314
7624
  const preview = new TextDecoder().decode(bytes.subarray(0, MAX_INLINE_TOOL_RESULT_BYTES));
@@ -7405,6 +7715,9 @@ var AgentRuntime = class _AgentRuntime {
7405
7715
  await options.bootstrap?.(runtime.resources);
7406
7716
  runtime.startHeartbeat();
7407
7717
  void runtime.pump();
7718
+ if (options.sessionFiles) {
7719
+ await startSessionFilesConsumer(runtime, options.sessionFiles);
7720
+ }
7408
7721
  return runtime;
7409
7722
  } catch (error) {
7410
7723
  await runtime.resources.closeExtensions().catch(() => void 0);
@@ -8149,7 +8462,7 @@ var AgentRuntime = class _AgentRuntime {
8149
8462
  onContext: assembly.setContext,
8150
8463
  runtime: {
8151
8464
  interact: (request) => this.liveInteract(run.id, request, controller.signal),
8152
- tools: ({ toolCall, contentBlocks, driver, options, providerInitiated }) => {
8465
+ tools: ({ toolCall, contentBlocks, driver, options, providerInitiated, model: callModel, usage: callUsage }) => {
8153
8466
  const task = toolQueue.then(async () => {
8154
8467
  if (!providerInitiated) {
8155
8468
  const execution = await this.executeToolBatch({
@@ -8163,6 +8476,8 @@ var AgentRuntime = class _AgentRuntime {
8163
8476
  signal: controller.signal,
8164
8477
  driver,
8165
8478
  options,
8479
+ model: callModel ?? model,
8480
+ usage: callUsage,
8166
8481
  onRevision: (rev) => {
8167
8482
  executionRevision = rev;
8168
8483
  }
@@ -8187,6 +8502,8 @@ var AgentRuntime = class _AgentRuntime {
8187
8502
  options,
8188
8503
  providerInitiated: true,
8189
8504
  interactionIds,
8505
+ model: callModel ?? model,
8506
+ usage: callUsage,
8190
8507
  onRevision: (rev) => {
8191
8508
  executionRevision = rev;
8192
8509
  }
@@ -8277,7 +8594,7 @@ var AgentRuntime = class _AgentRuntime {
8277
8594
  toolQueue = task.then(() => void 0, () => void 0);
8278
8595
  return task;
8279
8596
  },
8280
- toolsBatch: ({ toolCalls, contentBlocks, driver, options }) => {
8597
+ toolsBatch: ({ toolCalls, contentBlocks, driver, options, model: batchModel, usage: batchUsage }) => {
8281
8598
  const task = toolQueue.then(async () => {
8282
8599
  const execution = await this.executeToolBatch({
8283
8600
  run,
@@ -8290,6 +8607,8 @@ var AgentRuntime = class _AgentRuntime {
8290
8607
  signal: controller.signal,
8291
8608
  driver,
8292
8609
  options,
8610
+ model: batchModel ?? model,
8611
+ usage: batchUsage,
8293
8612
  onRevision: (rev) => {
8294
8613
  executionRevision = rev;
8295
8614
  }
@@ -8517,6 +8836,8 @@ var AgentRuntime = class _AgentRuntime {
8517
8836
  expectedRevision: revision,
8518
8837
  requestMessageId: createId("msg"),
8519
8838
  contentBlocks: input.contentBlocks,
8839
+ ...input.model ? { model: input.model } : {},
8840
+ ...input.usage ? { usage: input.usage } : {},
8520
8841
  calls: input.toolCalls.map((tc, i) => ({
8521
8842
  toolCallId: toolCallIds[i],
8522
8843
  providerToolCallId: tc.id,
@@ -8652,6 +8973,8 @@ var AgentRuntime = class _AgentRuntime {
8652
8973
  expectedRevision: revision,
8653
8974
  requestMessageId: createId("msg"),
8654
8975
  contentBlocks: input.contentBlocks,
8976
+ ...input.model ? { model: input.model } : {},
8977
+ ...input.usage ? { usage: input.usage } : {},
8655
8978
  calls: [{
8656
8979
  toolCallId,
8657
8980
  providerToolCallId,
@@ -8749,6 +9072,8 @@ var AgentRuntime = class _AgentRuntime {
8749
9072
  expectedRevision: revision,
8750
9073
  requestMessageId: createId("msg"),
8751
9074
  contentBlocks: input.contentBlocks,
9075
+ ...input.model ? { model: input.model } : {},
9076
+ ...input.usage ? { usage: input.usage } : {},
8752
9077
  calls: [{
8753
9078
  toolCallId,
8754
9079
  providerToolCallId,
@@ -8770,6 +9095,8 @@ var AgentRuntime = class _AgentRuntime {
8770
9095
  expectedRevision: revision,
8771
9096
  requestMessageId: createId("msg"),
8772
9097
  contentBlocks: input.contentBlocks,
9098
+ ...input.model ? { model: input.model } : {},
9099
+ ...input.usage ? { usage: input.usage } : {},
8773
9100
  calls: [{
8774
9101
  toolCallId,
8775
9102
  providerToolCallId,
@@ -8824,6 +9151,8 @@ var AgentRuntime = class _AgentRuntime {
8824
9151
  expectedRevision: revision,
8825
9152
  requestMessageId: createId("msg"),
8826
9153
  contentBlocks: input.contentBlocks,
9154
+ ...input.model ? { model: input.model } : {},
9155
+ ...input.usage ? { usage: input.usage } : {},
8827
9156
  calls: [{
8828
9157
  toolCallId,
8829
9158
  providerToolCallId,
@@ -8873,6 +9202,8 @@ var AgentRuntime = class _AgentRuntime {
8873
9202
  expectedRevision: revision,
8874
9203
  requestMessageId: createId("msg"),
8875
9204
  contentBlocks: input.contentBlocks,
9205
+ ...input.model ? { model: input.model } : {},
9206
+ ...input.usage ? { usage: input.usage } : {},
8876
9207
  calls: [{
8877
9208
  toolCallId,
8878
9209
  providerToolCallId,
@@ -9197,7 +9528,7 @@ ${content}`;
9197
9528
  archiveDirFor(agentId) {
9198
9529
  const store = this.owned;
9199
9530
  const archiveDir = store.contextArchiveDir?.(agentId);
9200
- return archiveDir ? join5(archiveDir, "tool-results") : void 0;
9531
+ return archiveDir ? join6(archiveDir, "tool-results") : void 0;
9201
9532
  }
9202
9533
  async runConsumer(subscription) {
9203
9534
  const registration = await this.owned.openConsumer(subscription.consumerId);
@@ -10355,6 +10686,8 @@ var InMemoryStore = class _InMemoryStore {
10355
10686
  execution: input.execution,
10356
10687
  expectedRevision: input.expectedRevision,
10357
10688
  requestMessageId: input.requestMessageId,
10689
+ ...input.model ? { model: input.model } : {},
10690
+ ...input.usage ? { usage: input.usage } : {},
10358
10691
  calls: [{
10359
10692
  providerToolCallId,
10360
10693
  name: input.name,
@@ -10386,7 +10719,9 @@ var InMemoryStore = class _InMemoryStore {
10386
10719
  input.execution.executionId,
10387
10720
  input.requestMessageId,
10388
10721
  input.calls,
10389
- input.contentBlocks ?? null
10722
+ input.contentBlocks ?? null,
10723
+ input.model ?? null,
10724
+ input.usage ?? null
10390
10725
  ]);
10391
10726
  const replay = this.replayOperation(operationKey, operationPayload);
10392
10727
  if (replay) return clone(replay);
@@ -10428,7 +10763,9 @@ var InMemoryStore = class _InMemoryStore {
10428
10763
  role: "assistant",
10429
10764
  content: requestMessageContent(toolCalls, input.contentBlocks),
10430
10765
  sequenceWithinRun: this.nextMessageSequence(run.id),
10431
- createdAt: timestamp
10766
+ createdAt: timestamp,
10767
+ ...input.model ? { model: input.model } : {},
10768
+ ...input.usage ? { usage: input.usage } : {}
10432
10769
  };
10433
10770
  for (const toolCall of toolCalls) this.storeToolCall(toolCall);
10434
10771
  this.storeMessage(requestMessage);
@@ -11480,8 +11817,8 @@ function requestMessageContent(toolCalls, contentBlocks) {
11480
11817
 
11481
11818
  // src/runtime/sqlite-durable-store.ts
11482
11819
  import { Database } from "bun:sqlite";
11483
- import { appendFileSync, chmodSync, mkdirSync, readFileSync as readFileSync2, rmSync } from "fs";
11484
- import { dirname as dirname7, join as join6, resolve as resolve8 } from "path";
11820
+ import { appendFileSync as appendFileSync2, chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync } from "fs";
11821
+ import { dirname as dirname8, join as join7, resolve as resolve8 } from "path";
11485
11822
  import { tmpdir as tmpdir2 } from "os";
11486
11823
  var SCHEMA_ID = "rowan.agent.runtime";
11487
11824
  var SCHEMA_VERSION = "2";
@@ -11702,7 +12039,7 @@ var SqliteStore = class {
11702
12039
  stmtUpdateState;
11703
12040
  constructor(filename = ":memory:") {
11704
12041
  this.database = new Database(filename, { create: true, readwrite: true, strict: true });
11705
- this.archiveRoot = filename === ":memory:" ? join6(tmpdir2(), `rowan-context-archives-${crypto.randomUUID()}`) : join6(dirname7(resolve8(filename)), "context-archives");
12042
+ this.archiveRoot = filename === ":memory:" ? join7(tmpdir2(), `rowan-context-archives-${crypto.randomUUID()}`) : join7(dirname8(resolve8(filename)), "context-archives");
11706
12043
  }
11707
12044
  async openOwner(input) {
11708
12045
  this.assertOpen();
@@ -11796,7 +12133,7 @@ var SqliteStore = class {
11796
12133
  }
11797
12134
  /** Filesystem location owned by this Store for one Agent's readable archive. */
11798
12135
  contextArchiveDir(agentId) {
11799
- return join6(this.archiveRoot, String(agentId));
12136
+ return join7(this.archiveRoot, String(agentId));
11800
12137
  }
11801
12138
  async reserveAgent(lease, input) {
11802
12139
  return this.invoke(lease, (store, current) => store.reserveAgent(current, input));
@@ -12392,7 +12729,7 @@ var SqliteStore = class {
12392
12729
  mirrorChanges(tracker) {
12393
12730
  for (const agentId of tracker.deletedAgentIds) {
12394
12731
  const idStr = String(agentId);
12395
- rmSync(join6(this.archiveRoot, idStr), { recursive: true, force: true });
12732
+ rmSync(join7(this.archiveRoot, idStr), { recursive: true, force: true });
12396
12733
  this.mirroredEventIds.delete(idStr);
12397
12734
  this.mirroredCompactionIds.delete(idStr);
12398
12735
  this.mirroredAgents.delete(idStr);
@@ -12400,15 +12737,15 @@ var SqliteStore = class {
12400
12737
  for (const agent of tracker.upsertedAgents.values()) {
12401
12738
  const idStr = String(agent.id);
12402
12739
  this.mirroredAgents.add(idStr);
12403
- const archiveDir = join6(this.archiveRoot, idStr);
12404
- const toolDir = join6(archiveDir, "tool-results");
12405
- mkdirSync(toolDir, { recursive: true, mode: 448 });
12406
- chmodSync(archiveDir, 448);
12407
- chmodSync(toolDir, 448);
12740
+ const archiveDir = join7(this.archiveRoot, idStr);
12741
+ const toolDir = join7(archiveDir, "tool-results");
12742
+ mkdirSync2(toolDir, { recursive: true, mode: 448 });
12743
+ chmodSync2(archiveDir, 448);
12744
+ chmodSync2(toolDir, 448);
12408
12745
  }
12409
12746
  if (tracker.appendedEvents.length > 0) {
12410
- mkdirSync(this.archiveRoot, { recursive: true, mode: 448 });
12411
- chmodSync(this.archiveRoot, 448);
12747
+ mkdirSync2(this.archiveRoot, { recursive: true, mode: 448 });
12748
+ chmodSync2(this.archiveRoot, 448);
12412
12749
  const eventsByAgent = /* @__PURE__ */ new Map();
12413
12750
  for (const event of tracker.appendedEvents) {
12414
12751
  const agentId = String(event.agentId);
@@ -12420,22 +12757,22 @@ var SqliteStore = class {
12420
12757
  list.push(event);
12421
12758
  }
12422
12759
  for (const [agentId, events] of eventsByAgent) {
12423
- const archiveDir = join6(this.archiveRoot, agentId);
12424
- const toolDir = join6(archiveDir, "tool-results");
12425
- mkdirSync(toolDir, { recursive: true, mode: 448 });
12426
- chmodSync(archiveDir, 448);
12427
- chmodSync(toolDir, 448);
12428
- const sessionPath = join6(archiveDir, "session.jsonl");
12760
+ const archiveDir = join7(this.archiveRoot, agentId);
12761
+ const toolDir = join7(archiveDir, "tool-results");
12762
+ mkdirSync2(toolDir, { recursive: true, mode: 448 });
12763
+ chmodSync2(archiveDir, 448);
12764
+ chmodSync2(toolDir, 448);
12765
+ const sessionPath = join7(archiveDir, "session.jsonl");
12429
12766
  try {
12430
- chmodSync(sessionPath, 384);
12767
+ chmodSync2(sessionPath, 384);
12431
12768
  } catch {
12432
12769
  }
12433
12770
  const known = this.mirroredEventIds.get(agentId) ?? /* @__PURE__ */ new Set();
12434
12771
  const newEvents = events.filter((e) => !known.has(String(e.id)));
12435
12772
  if (newEvents.length > 0) {
12436
12773
  const lines = newEvents.map((e) => JSON.stringify({ schemaVersion: 1, kind: "rowan_event", event: e }) + "\n").join("");
12437
- appendFileSync(sessionPath, lines, { encoding: "utf8", mode: 384 });
12438
- chmodSync(sessionPath, 384);
12774
+ appendFileSync2(sessionPath, lines, { encoding: "utf8", mode: 384 });
12775
+ chmodSync2(sessionPath, 384);
12439
12776
  for (const e of newEvents) known.add(String(e.id));
12440
12777
  this.mirroredEventIds.set(agentId, known);
12441
12778
  }
@@ -12444,13 +12781,13 @@ var SqliteStore = class {
12444
12781
  if (tracker.upsertedContextCompactions.size > 0) {
12445
12782
  for (const [agentId, record] of tracker.upsertedContextCompactions) {
12446
12783
  const idStr = String(agentId);
12447
- const archiveDir = join6(this.archiveRoot, idStr);
12448
- const sessionPath = join6(archiveDir, "session.jsonl");
12784
+ const archiveDir = join7(this.archiveRoot, idStr);
12785
+ const sessionPath = join7(archiveDir, "session.jsonl");
12449
12786
  const knownCompactions = this.mirroredCompactionIds.get(idStr) ?? /* @__PURE__ */ new Set();
12450
12787
  if (!knownCompactions.has(record.id)) {
12451
12788
  const line = JSON.stringify({ schemaVersion: 1, kind: "context_compacted", record }) + "\n";
12452
- appendFileSync(sessionPath, line, { encoding: "utf8", mode: 384 });
12453
- chmodSync(sessionPath, 384);
12789
+ appendFileSync2(sessionPath, line, { encoding: "utf8", mode: 384 });
12790
+ chmodSync2(sessionPath, 384);
12454
12791
  knownCompactions.add(record.id);
12455
12792
  this.mirroredCompactionIds.set(idStr, knownCompactions);
12456
12793
  }
@@ -12459,32 +12796,32 @@ var SqliteStore = class {
12459
12796
  }
12460
12797
  /** Append the semantic durable event mirror and remove deleted Agent sidecars. */
12461
12798
  mirrorInitialState(state) {
12462
- mkdirSync(this.archiveRoot, { recursive: true, mode: 448 });
12463
- chmodSync(this.archiveRoot, 448);
12799
+ mkdirSync2(this.archiveRoot, { recursive: true, mode: 448 });
12800
+ chmodSync2(this.archiveRoot, 448);
12464
12801
  const currentAgents = new Set(state.agents.map((agent) => String(agent.id)));
12465
12802
  for (const agentId of this.mirroredAgents) {
12466
12803
  if (currentAgents.has(agentId)) continue;
12467
- rmSync(join6(this.archiveRoot, agentId), { recursive: true, force: true });
12804
+ rmSync(join7(this.archiveRoot, agentId), { recursive: true, force: true });
12468
12805
  this.mirroredEventIds.delete(agentId);
12469
12806
  this.mirroredCompactionIds.delete(agentId);
12470
12807
  }
12471
12808
  for (const agent of state.agents) {
12472
12809
  const agentId = String(agent.id);
12473
- const archiveDir = join6(this.archiveRoot, agentId);
12474
- const toolDir = join6(archiveDir, "tool-results");
12475
- mkdirSync(toolDir, { recursive: true, mode: 448 });
12476
- chmodSync(archiveDir, 448);
12477
- chmodSync(toolDir, 448);
12478
- const sessionPath = join6(archiveDir, "session.jsonl");
12810
+ const archiveDir = join7(this.archiveRoot, agentId);
12811
+ const toolDir = join7(archiveDir, "tool-results");
12812
+ mkdirSync2(toolDir, { recursive: true, mode: 448 });
12813
+ chmodSync2(archiveDir, 448);
12814
+ chmodSync2(toolDir, 448);
12815
+ const sessionPath = join7(archiveDir, "session.jsonl");
12479
12816
  try {
12480
- chmodSync(sessionPath, 384);
12817
+ chmodSync2(sessionPath, 384);
12481
12818
  } catch {
12482
12819
  }
12483
12820
  const known = this.mirroredEventIds.get(agentId) ?? /* @__PURE__ */ new Set();
12484
12821
  const knownCompactions = this.mirroredCompactionIds.get(agentId) ?? /* @__PURE__ */ new Set();
12485
12822
  if (known.size === 0 || knownCompactions.size === 0) {
12486
12823
  try {
12487
- for (const line of readFileSync2(sessionPath, "utf8").split("\n")) {
12824
+ for (const line of readFileSync3(sessionPath, "utf8").split("\n")) {
12488
12825
  if (!line.trim()) continue;
12489
12826
  const parsed = JSON.parse(line);
12490
12827
  if (parsed.event?.id) known.add(parsed.event.id);
@@ -12495,16 +12832,16 @@ var SqliteStore = class {
12495
12832
  }
12496
12833
  const lines = state.events.filter((event) => String(event.agentId) === agentId && !known.has(String(event.id))).map((event) => JSON.stringify({ schemaVersion: 1, kind: "rowan_event", event }) + "\n");
12497
12834
  if (lines.length > 0) {
12498
- appendFileSync(sessionPath, lines.join(""), { encoding: "utf8", mode: 384 });
12499
- chmodSync(sessionPath, 384);
12835
+ appendFileSync2(sessionPath, lines.join(""), { encoding: "utf8", mode: 384 });
12836
+ chmodSync2(sessionPath, 384);
12500
12837
  for (const event of state.events) {
12501
12838
  if (String(event.agentId) === agentId) known.add(String(event.id));
12502
12839
  }
12503
12840
  }
12504
12841
  const compactionLines = (state.contextCompactions ?? []).filter(([candidateId, record]) => String(candidateId) === agentId && !knownCompactions.has(record.id)).map(([, record]) => JSON.stringify({ schemaVersion: 1, kind: "context_compacted", record }) + "\n");
12505
12842
  if (compactionLines.length > 0) {
12506
- appendFileSync(sessionPath, compactionLines.join(""), { encoding: "utf8", mode: 384 });
12507
- chmodSync(sessionPath, 384);
12843
+ appendFileSync2(sessionPath, compactionLines.join(""), { encoding: "utf8", mode: 384 });
12844
+ chmodSync2(sessionPath, 384);
12508
12845
  for (const [, record] of state.contextCompactions ?? []) {
12509
12846
  if (String(record.agentId) === agentId) knownCompactions.add(record.id);
12510
12847
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,7 +23,7 @@
23
23
  "build": "tsup && bun scripts/check-public-interface.ts"
24
24
  },
25
25
  "dependencies": {
26
- "@rowan-agent/models": "0.9.0",
26
+ "@rowan-agent/models": "0.10.0",
27
27
  "jiti": "2.7.0",
28
28
  "typebox": "^1.0.0",
29
29
  "yaml": "^2.7.0"