tnega 0.3.0 → 0.4.1

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/eval.js CHANGED
@@ -8190,17 +8190,88 @@ function checkMonotonicSeq(events) {
8190
8190
  }
8191
8191
  return failures;
8192
8192
  }
8193
+ function isRecord(value) {
8194
+ return typeof value === "object" && value !== null && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
8195
+ }
8196
+ function isNonNegativeInteger(value) {
8197
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
8198
+ }
8199
+ function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
8200
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
8201
+ if (typeof value === "number") return Number.isFinite(value);
8202
+ if (typeof value !== "object" || ancestors.has(value)) return false;
8203
+ if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
8204
+ ancestors.add(value);
8205
+ const valid = (Array.isArray(value) ? Array.from(value) : Object.values(value)).every((member) => isJsonValue(member, ancestors));
8206
+ ancestors.delete(value);
8207
+ return valid;
8208
+ }
8209
+ function isStreamRecord(value) {
8210
+ if (!isRecord(value) || !isNonNegativeInteger(value.time) || !isRecord(value.chunk)) return false;
8211
+ const chunk = value.chunk;
8212
+ if (chunk.type === "stream_error") {
8213
+ return isRecord(chunk.error) && typeof chunk.error.message === "string" && (chunk.error.name === void 0 || typeof chunk.error.name === "string") && (chunk.error.stack === void 0 || typeof chunk.error.stack === "string");
8214
+ }
8215
+ if (typeof chunk.id !== "string") return false;
8216
+ switch (chunk.type) {
8217
+ case "message_start":
8218
+ return chunk.model === void 0 || typeof chunk.model === "string";
8219
+ case "message_delta":
8220
+ return typeof chunk.delta === "string";
8221
+ case "toolcall_start":
8222
+ case "toolcall_end":
8223
+ return isNonNegativeInteger(chunk.index) && typeof chunk.name === "string" && (chunk.type === "toolcall_start" || isJsonValue(chunk.arguments));
8224
+ case "message_stop":
8225
+ return chunk.finishReason === "stop" || chunk.finishReason === "tool_calls" || chunk.finishReason === "length" || chunk.finishReason === "max_turns" || chunk.finishReason === "max_steps" || chunk.finishReason === "error" || chunk.finishReason === "cancelled";
8226
+ default:
8227
+ return false;
8228
+ }
8229
+ }
8230
+ function checkAssistantAttempts(events) {
8231
+ const turns = /* @__PURE__ */ new Set();
8232
+ const steps = /* @__PURE__ */ new Map();
8233
+ const failures = [];
8234
+ for (const event of events) {
8235
+ if (event.type === "turn/start") turns.add(event.payload.turn);
8236
+ if (event.type === "turn/end") {
8237
+ turns.delete(event.payload.turn);
8238
+ for (const [key, turn] of steps) {
8239
+ if (turn === event.payload.turn) steps.delete(key);
8240
+ }
8241
+ }
8242
+ if (event.type === "step/start" && turns.has(event.payload.turn)) {
8243
+ steps.set(`${event.payload.turn}:${event.payload.step}`, event.payload.turn);
8244
+ }
8245
+ if (event.type === "step/end") steps.delete(`${event.payload.turn}:${event.payload.step}`);
8246
+ if (event.type !== "assistant/attempt") continue;
8247
+ const payload = event.payload;
8248
+ if (!isRecord(payload) || !isNonNegativeInteger(payload.turn) || !isNonNegativeInteger(payload.step) || !turns.has(payload.turn) || !steps.has(`${payload.turn}:${payload.step}`)) {
8249
+ failures.push({
8250
+ name: "assistant/attempt-without-open-step",
8251
+ detail: `assistant/attempt at seq ${event.seq} has no matching open turn and step`
8252
+ });
8253
+ }
8254
+ if (!isRecord(payload) || !Array.isArray(payload.stream) || !payload.stream.every(isStreamRecord)) {
8255
+ failures.push({
8256
+ name: "assistant/attempt-invalid-stream",
8257
+ detail: `assistant/attempt at seq ${event.seq} must contain an ordered array of normalized stream records`
8258
+ });
8259
+ }
8260
+ }
8261
+ return failures;
8262
+ }
8193
8263
  function checkSessionInvariants(events) {
8194
8264
  return [
8195
8265
  ...checkBalancedTurns(events),
8196
8266
  ...checkBalancedSteps(events),
8197
8267
  ...checkBalancedToolCalls(events),
8198
- ...checkMonotonicSeq(events)
8268
+ ...checkMonotonicSeq(events),
8269
+ ...checkAssistantAttempts(events)
8199
8270
  ];
8200
8271
  }
8201
8272
 
8202
8273
  // packages/session/src/index.ts
8203
- var SESSION_FORMAT_VERSION = 7;
8274
+ var SESSION_FORMAT_VERSION = 10;
8204
8275
  var SessionFormatError = class extends Error {
8205
8276
  name = "SessionFormatError";
8206
8277
  };
@@ -8415,6 +8486,7 @@ function estimateEventTokens(event) {
8415
8486
  return Math.ceil(raw.length / 4);
8416
8487
  }
8417
8488
  case "plan":
8489
+ case "assistant/attempt":
8418
8490
  return 0;
8419
8491
  case "checkpoint":
8420
8492
  return estimateMessageTokens(event.payload.messages);
@@ -8549,9 +8621,15 @@ function estimateContextUsage(messages, limit = DEFAULT_CONTEXT_LIMIT) {
8549
8621
  };
8550
8622
  }
8551
8623
  var SessionLog = class {
8552
- constructor(file, _broadcast) {
8624
+ constructor(file, _broadcast, publicationContext) {
8553
8625
  this.file = file;
8554
8626
  this._broadcast = _broadcast;
8627
+ if (!this._broadcast && publicationContext) {
8628
+ this._broadcast = (type, payload) => {
8629
+ if (type === "event") publicationContext.emit("session/event", payload);
8630
+ else publicationContext.emit("session/flush", payload);
8631
+ };
8632
+ }
8555
8633
  }
8556
8634
  file;
8557
8635
  _broadcast;
@@ -8720,7 +8798,7 @@ var SessionLog = class {
8720
8798
  selected.push(clone(event));
8721
8799
  continue;
8722
8800
  }
8723
- if (event.type === "meta" || event.type === "llm/retry" || event.type === "llm/retry-started" || event.type.startsWith("turn/") || event.type.startsWith("step/")) {
8801
+ if (event.type === "meta" || event.type === "llm/retry" || event.type === "llm/retry-started" || event.type === "assistant/attempt" || event.type.startsWith("turn/") || event.type.startsWith("step/")) {
8724
8802
  continue;
8725
8803
  }
8726
8804
  if (event.type === "checkpoint") {
@@ -11614,7 +11692,7 @@ function copyMessages(messages) {
11614
11692
  copy.tool_calls = message2.tool_calls.map((call) => ({
11615
11693
  id: call.id,
11616
11694
  name: call.name,
11617
- arguments: call.arguments
11695
+ arguments: structuredClone(call.arguments)
11618
11696
  }));
11619
11697
  }
11620
11698
  return copy;
@@ -11645,6 +11723,75 @@ function copyCompletion(completion) {
11645
11723
  }
11646
11724
  return copy;
11647
11725
  }
11726
+ function canonicalMessages(messages) {
11727
+ return JSON.stringify(messages.map((message2) => ({
11728
+ role: message2.role,
11729
+ content: message2.content,
11730
+ ...message2.name ? { name: message2.name } : {},
11731
+ ...message2.tool_call_id ? { tool_call_id: message2.tool_call_id } : {},
11732
+ ...message2.tool_calls?.length ? { tool_calls: message2.tool_calls } : {},
11733
+ ...message2.toolOk !== void 0 ? { toolOk: message2.toolOk } : {},
11734
+ ...message2.toolError ? {
11735
+ toolError: { name: message2.toolError.name, message: message2.toolError.message }
11736
+ } : {}
11737
+ })));
11738
+ }
11739
+ function freezeRequestData(data) {
11740
+ const visited = /* @__PURE__ */ new Set();
11741
+ const freeze = (value) => {
11742
+ if (typeof value !== "object" || value === null || visited.has(value)) return;
11743
+ visited.add(value);
11744
+ for (const child of Object.values(value)) freeze(child);
11745
+ Object.freeze(value);
11746
+ };
11747
+ freeze(data);
11748
+ }
11749
+ function lockStreamRequest(request) {
11750
+ freezeRequestData(request.messages);
11751
+ request.tools = Object.freeze(request.tools.map((tool) => {
11752
+ const schema = structuredClone(tool.schema);
11753
+ freezeRequestData(schema);
11754
+ return Object.freeze({ ...tool, schema });
11755
+ }));
11756
+ Object.freeze(request.options);
11757
+ Object.freeze(request);
11758
+ }
11759
+ function requestHeaderFor(request, input) {
11760
+ const system = input.find((message2) => message2.role === "system")?.content;
11761
+ const tools2 = request.tools.map((tool) => ({
11762
+ name: tool.schema.name,
11763
+ description: tool.schema.description,
11764
+ ...tool.schema.parameters ? { parameters: tool.schema.parameters } : {}
11765
+ }));
11766
+ const config2 = {
11767
+ ...request.options.provider ? { provider: request.options.provider } : {},
11768
+ ...request.options.model ? { model: request.options.model } : {},
11769
+ ...request.options.temperature !== void 0 ? { temperature: request.options.temperature } : {}
11770
+ };
11771
+ return {
11772
+ ...Object.keys(config2).length ? { config: config2 } : {},
11773
+ ...system !== void 0 ? { system } : {},
11774
+ ...tools2.length ? { tools: tools2 } : {}
11775
+ };
11776
+ }
11777
+ async function* completeAsStream(llm, messages, tools2, options) {
11778
+ const id = randomUUID2();
11779
+ yield { type: "message_start", id };
11780
+ const completion = await llm.complete(messages, tools2, options);
11781
+ if (completion.content) yield { type: "message_delta", id, delta: completion.content };
11782
+ for (let index = 0; index < (completion.toolCalls?.length ?? 0); index += 1) {
11783
+ const call = completion.toolCalls[index];
11784
+ yield { type: "toolcall_start", id: call.id, index, name: call.name };
11785
+ yield {
11786
+ type: "toolcall_end",
11787
+ id: call.id,
11788
+ index,
11789
+ name: call.name,
11790
+ arguments: call.arguments
11791
+ };
11792
+ }
11793
+ yield { type: "message_stop", id, finishReason: completion.finishReason };
11794
+ }
11648
11795
  function isCancelCause(value) {
11649
11796
  if (typeof value !== "object" || value === null) return false;
11650
11797
  const record3 = value;
@@ -11665,6 +11812,13 @@ function cancelCauseFromSignal(signal) {
11665
11812
  if (isCancelCause(reason)) return reason;
11666
11813
  return { type: "abort" };
11667
11814
  }
11815
+ function abortedToolError(signal) {
11816
+ const cause = cancelCauseFromSignal(signal);
11817
+ return {
11818
+ name: "AbortError",
11819
+ message: `tool call aborted: ${cause?.type ?? "abort"}`
11820
+ };
11821
+ }
11668
11822
  function toTurnEndReason(fields) {
11669
11823
  const { cancelled, turnError, finishReason, cancelCause } = fields;
11670
11824
  if (cancelled || finishReason === "cancelled") {
@@ -11709,6 +11863,78 @@ async function cancellableDelay(ms, signal) {
11709
11863
  signal?.addEventListener("abort", onAbort, { once: true });
11710
11864
  });
11711
11865
  }
11866
+ var AssistantStreamAttempt = class {
11867
+ constructor(session2, turn, step, nextRevision) {
11868
+ this.session = session2;
11869
+ this.turn = turn;
11870
+ this.step = step;
11871
+ this.nextRevision = nextRevision;
11872
+ }
11873
+ session;
11874
+ turn;
11875
+ step;
11876
+ nextRevision;
11877
+ attemptId = randomUUID2();
11878
+ records = [];
11879
+ ended = false;
11880
+ start() {
11881
+ return { type: "assistant/stream", frame: {
11882
+ type: "start",
11883
+ attemptId: this.attemptId,
11884
+ revision: this.nextRevision(),
11885
+ turn: this.turn,
11886
+ step: this.step
11887
+ } };
11888
+ }
11889
+ push(chunk) {
11890
+ if (this.ended) throw new AgentError("assistant attempt is already settled");
11891
+ const record3 = { time: Date.now(), chunk: structuredClone(chunk) };
11892
+ const index = this.records.length;
11893
+ this.records.push(record3);
11894
+ return { type: "assistant/stream", frame: {
11895
+ type: "chunk",
11896
+ attemptId: this.attemptId,
11897
+ revision: this.nextRevision(),
11898
+ index,
11899
+ ...structuredClone(record3)
11900
+ } };
11901
+ }
11902
+ async settle(message2) {
11903
+ if (this.ended) throw new AgentError("assistant attempt is already settled");
11904
+ const eventType = message2 ? "assistant/message" : "assistant/attempt";
11905
+ let event;
11906
+ try {
11907
+ event = message2 ? await this.session.append("assistant/message", { ...message2, stream: this.records }) : await this.session.append("assistant/attempt", { turn: this.turn, step: this.step, stream: this.records });
11908
+ } catch (cause) {
11909
+ this.ended = true;
11910
+ const frame = { type: "assistant/stream", frame: {
11911
+ type: "end",
11912
+ attemptId: this.attemptId,
11913
+ revision: this.nextRevision(),
11914
+ index: this.records.length,
11915
+ outcome: { kind: "abandoned" }
11916
+ } };
11917
+ throw new AttemptSettlementError(cause, frame);
11918
+ }
11919
+ this.ended = true;
11920
+ return { type: "assistant/stream", frame: {
11921
+ type: "end",
11922
+ attemptId: this.attemptId,
11923
+ revision: this.nextRevision(),
11924
+ index: this.records.length,
11925
+ outcome: { kind: "committed", eventType, seq: event.seq }
11926
+ } };
11927
+ }
11928
+ };
11929
+ var AttemptSettlementError = class extends Error {
11930
+ constructor(cause, frame) {
11931
+ super("assistant attempt settlement failed");
11932
+ this.cause = cause;
11933
+ this.frame = frame;
11934
+ }
11935
+ cause;
11936
+ frame;
11937
+ };
11712
11938
  var AgentService = class {
11713
11939
  constructor(ctx, config2 = {}) {
11714
11940
  this.ctx = ctx;
@@ -11717,9 +11943,24 @@ var AgentService = class {
11717
11943
  }
11718
11944
  ctx;
11719
11945
  config;
11946
+ async *settleAttempt(attempt, message2) {
11947
+ try {
11948
+ yield await attempt.settle(message2);
11949
+ } catch (error2) {
11950
+ if (error2 instanceof AttemptSettlementError) {
11951
+ yield error2.frame;
11952
+ throw error2.cause;
11953
+ }
11954
+ throw error2;
11955
+ }
11956
+ }
11720
11957
  inbox;
11721
11958
  _seriesStarted = false;
11722
11959
  _persistedRequest = false;
11960
+ _streamRevision = 0;
11961
+ _attempt(session2, turn, step) {
11962
+ return new AssistantStreamAttempt(session2, turn, step, () => ++this._streamRevision);
11963
+ }
11723
11964
  /** Call when a request begins an explicit new model-message series. */
11724
11965
  startSeries() {
11725
11966
  this._seriesStarted = true;
@@ -11733,7 +11974,7 @@ var AgentService = class {
11733
11974
  return this._persistedRequest;
11734
11975
  }
11735
11976
  async run(input, options = {}) {
11736
- return consumeAgentStream(this._stream(input, options, false));
11977
+ return consumeAgentStream(this._stream(input, options));
11737
11978
  }
11738
11979
  async *runStream(input, options = {}) {
11739
11980
  const iterator = this._stream(input, options)[Symbol.asyncIterator]();
@@ -11743,7 +11984,7 @@ var AgentService = class {
11743
11984
  yield next.value;
11744
11985
  }
11745
11986
  }
11746
- async *_stream(input, options = {}, useStream = true) {
11987
+ async *_stream(input, options = {}) {
11747
11988
  const claimed = input ?? this.inbox.claim();
11748
11989
  if (!claimed) throw new AgentError("no agent input available");
11749
11990
  await this.config.hooks?.beforeRun?.(claimed, options);
@@ -11751,26 +11992,32 @@ var AgentService = class {
11751
11992
  const tools2 = this._tools();
11752
11993
  const llm = this._llm();
11753
11994
  if (!llm) throw new AgentError("no LLM adapter available");
11995
+ const streamAdapter = llm.stream;
11996
+ const persistChunks = streamAdapter !== void 0;
11754
11997
  const maxTurns = options.maxTurns ?? this.config.maxTurns ?? 64;
11755
11998
  const maxSteps = options.maxSteps ?? this.config.maxSteps ?? 64;
11756
11999
  const contextBudget = options.contextBudget ?? this.config.contextBudget;
11757
12000
  const injected = this.inbox.injected();
11758
12001
  this.ctx.emit("agent/start", { input: claimed, options, injected });
11759
12002
  let messages = await this._initialMessages(claimed);
12003
+ let claimedMessages = messages.filter((message2) => message2.role === "user");
11760
12004
  const steps = [];
11761
12005
  let output = "";
11762
12006
  let finishReason = "stop";
12007
+ let lengthReached = false;
11763
12008
  this.ctx.emit("agent/turn-start", {
11764
12009
  input: claimed,
11765
12010
  messages: copyMessages(messages),
11766
12011
  injected
11767
12012
  });
11768
- const turn = await session2.nextTurn();
11769
- await session2.append("turn/start", {
11770
- turn,
11771
- input: claimed.text ?? claimed,
11772
- reason: "user"
11773
- });
12013
+ const turn = options.turn ?? await session2.nextTurn();
12014
+ if (options.turn === void 0) {
12015
+ await session2.append("turn/start", {
12016
+ turn,
12017
+ input: claimed.text ?? claimed,
12018
+ reason: "user"
12019
+ });
12020
+ }
11774
12021
  let index = 0;
11775
12022
  let finalTurnGranted = false;
11776
12023
  let currentStepIndex;
@@ -11789,20 +12036,22 @@ var AgentService = class {
11789
12036
  messages = await this._enforceContextBudget(session2, contextBudget, messages);
11790
12037
  }
11791
12038
  const stepInput = copyMessages(messages);
11792
- const preStep = this.ctx.waterfall("agent/pre-step", {
12039
+ const preStep = await this.ctx.waterfallAsync("agent/pre-step", {
11793
12040
  index,
11794
12041
  turn,
11795
12042
  step: index,
11796
12043
  messages: stepInput,
12044
+ claimedMessages: copyMessages(claimedMessages),
11797
12045
  ...options.signal ? { signal: options.signal } : {}
11798
12046
  }, (payload) => payload);
11799
- if (!preStep || !Array.isArray(preStep.messages) || !preStep.messages.length) {
12047
+ if (!preStep || preStep.admission === "reject" || !Array.isArray(preStep.messages) || !preStep.messages.length) {
11800
12048
  break;
11801
12049
  }
11802
12050
  if (preStep.startsRequestSeries) this._seriesStarted = true;
11803
12051
  await session2.append("step/start", { turn, step: index });
11804
12052
  currentStepIndex = index;
11805
12053
  const requestedInput = copyMessages(preStep.messages);
12054
+ const admittedHistory = await session2.deriveMessages();
11806
12055
  this.ctx.emit("agent/step", {
11807
12056
  index,
11808
12057
  turn,
@@ -11814,93 +12063,85 @@ var AgentService = class {
11814
12063
  maxSteps: maxSteps - steps.length
11815
12064
  };
11816
12065
  if (options.signal) completeOptions.signal = options.signal;
11817
- const request = this.ctx.waterfall("agent/request", {
11818
- index,
11819
- messages: requestedInput,
11820
- tools: availableTools,
11821
- options: completeOptions
11822
- }, (payload) => payload);
11823
- if (!request || !request.options || !request.tools) {
11824
- throw new AgentError("agent/request must return a request payload");
11825
- }
11826
12066
  let llmMessages = copyMessages(requestedInput);
11827
12067
  let completion;
11828
- const streamMethod = useStream ? llm.stream : void 0;
12068
+ let activeAttempt;
11829
12069
  let attempt = 0;
11830
- let persistedStepInput = false;
11831
12070
  while (true) {
11832
12071
  if (options.signal?.aborted) {
11833
12072
  finishReason = "cancelled";
11834
12073
  break;
11835
12074
  }
11836
12075
  const streamEvents = [];
12076
+ activeAttempt = this._attempt(session2, turn, index);
12077
+ yield activeAttempt.start();
12078
+ const streamRequest = {
12079
+ index,
12080
+ messages: copyMessages(requestedInput),
12081
+ tools: [...availableTools],
12082
+ options: { ...completeOptions }
12083
+ };
11837
12084
  try {
11838
- if (streamMethod) {
11839
- const streamRequest = {
11840
- index,
11841
- messages: llmMessages,
11842
- tools: request.tools,
11843
- options: request.options
11844
- };
11845
- const stream = await this.ctx.waterfallAsync(
11846
- "llm/stream",
11847
- streamRequest,
11848
- async (payload) => {
11849
- if (!streamMethod) {
11850
- throw new AgentError("llm.stream is required for streaming runs");
11851
- }
11852
- return streamMethod(payload.messages, payload.tools, payload.options);
11853
- }
11854
- );
11855
- if (!stream || typeof stream[Symbol.asyncIterator] !== "function") {
11856
- throw new AgentError("llm/stream must return an async iterable stream");
11857
- }
12085
+ const request = await this.ctx.waterfallAsync("agent/request", {
12086
+ index,
12087
+ messages: copyMessages(requestedInput),
12088
+ tools: streamRequest.tools,
12089
+ options: streamRequest.options
12090
+ }, (payload) => payload);
12091
+ if (!request || !request.options || !request.tools) {
12092
+ throw new AgentError("agent/request must return a request payload");
12093
+ }
12094
+ streamRequest.tools = request.tools;
12095
+ streamRequest.options = request.options;
12096
+ let preparation;
12097
+ const prepareRequest = () => preparation ??= (async () => {
12098
+ lockStreamRequest(streamRequest);
11858
12099
  llmMessages = copyMessages(streamRequest.messages);
11859
- if (!persistedStepInput) {
11860
- await this._persistStepInput(session2, request, llmMessages);
11861
- if (this.config.assertReplayable) {
11862
- await this._assertReplayable(session2, llmMessages);
11863
- }
11864
- persistedStepInput = true;
11865
- }
11866
- let chunkIndex = 0;
11867
- for await (const event of stream) {
11868
- streamEvents.push(event);
11869
- if (event.type === "message_delta") {
11870
- await session2.append("assistant/chunk", {
11871
- id: event.id,
11872
- content: event.delta,
11873
- index: chunkIndex
11874
- });
11875
- chunkIndex += 1;
11876
- }
11877
- yield event;
11878
- }
11879
- completion = completionFromStreamEvents(streamEvents);
11880
- } else {
11881
- if (!persistedStepInput) {
11882
- await this._persistStepInput(session2, request, llmMessages);
11883
- if (this.config.assertReplayable) {
11884
- await this._assertReplayable(session2, llmMessages);
11885
- }
11886
- persistedStepInput = true;
12100
+ await this._persistStepInput(session2, streamRequest, llmMessages, admittedHistory);
12101
+ await this._assertReplayable(session2, streamRequest, llmMessages);
12102
+ })();
12103
+ const stream = await this.ctx.waterfallAsync(
12104
+ "llm/stream",
12105
+ streamRequest,
12106
+ (payload) => (async function* () {
12107
+ await prepareRequest();
12108
+ yield* streamAdapter ? streamAdapter.call(llm, payload.messages, payload.tools, payload.options) : completeAsStream(llm, payload.messages, payload.tools, payload.options);
12109
+ })()
12110
+ );
12111
+ if (!stream || typeof stream[Symbol.asyncIterator] !== "function") {
12112
+ throw new AgentError("llm/stream must return an async iterable stream");
12113
+ }
12114
+ await prepareRequest();
12115
+ let chunkIndex = 0;
12116
+ for await (const event of stream) {
12117
+ streamEvents.push(structuredClone(event));
12118
+ yield activeAttempt.push(event);
12119
+ if (persistChunks && event.type === "message_delta") {
12120
+ await session2.append("assistant/chunk", {
12121
+ id: event.id,
12122
+ content: event.delta,
12123
+ index: chunkIndex
12124
+ });
12125
+ chunkIndex += 1;
11887
12126
  }
11888
- completion = await llm.complete(llmMessages, request.tools, request.options);
12127
+ yield event;
12128
+ }
12129
+ completion = completionFromStreamEvents(streamEvents);
12130
+ if (completion.finishReason === "error" || completion.finishReason === "cancelled") {
12131
+ const terminalReason = completion.finishReason;
12132
+ completion = void 0;
12133
+ throw new AgentError(`LLM stream finished with ${terminalReason}`);
11889
12134
  }
11890
12135
  break;
11891
12136
  } catch (error2) {
11892
- const content = partialStreamContent(streamEvents);
11893
- if (content) {
11894
- await session2.append("assistant/message", {
11895
- content,
11896
- interrupted: true
11897
- });
11898
- llmMessages = await session2.deriveMessages();
11899
- }
12137
+ yield activeAttempt.push({ type: "stream_error", error: toToolError2(error2) });
11900
12138
  if (options.signal?.aborted) {
12139
+ const content = partialStreamContent(streamEvents);
12140
+ yield* this.settleAttempt(activeAttempt, content ? { content, interrupted: true } : void 0);
11901
12141
  finishReason = "cancelled";
11902
12142
  break;
11903
12143
  }
12144
+ yield* this.settleAttempt(activeAttempt);
11904
12145
  attempt += 1;
11905
12146
  const failure = toToolError2(error2);
11906
12147
  const decision = await this.ctx.waterfallAsync(
@@ -11909,14 +12150,14 @@ var AgentService = class {
11909
12150
  index,
11910
12151
  turn,
11911
12152
  step: index,
11912
- messages: copyMessages(llmMessages),
11913
- tools: request.tools,
11914
- options: request.options,
12153
+ messages: copyMessages(streamRequest.messages),
12154
+ tools: [...streamRequest.tools],
12155
+ options: { ...streamRequest.options },
11915
12156
  attempt,
11916
12157
  error: error2,
11917
12158
  failure,
11918
- ...request.options.provider ? { provider: request.options.provider } : {},
11919
- ...request.options.model ? { model: request.options.model } : {},
12159
+ ...streamRequest.options.provider ? { provider: streamRequest.options.provider } : {},
12160
+ ...streamRequest.options.model ? { model: streamRequest.options.model } : {},
11920
12161
  ...options.signal ? { signal: options.signal } : {}
11921
12162
  },
11922
12163
  () => void 0
@@ -11941,16 +12182,30 @@ var AgentService = class {
11941
12182
  await session2.append("llm/retry-started", { retryId, retry: attempt });
11942
12183
  }
11943
12184
  }
11944
- if (options.signal?.aborted) break;
11945
- if (!completion) throw new AgentError("LLM adapter did not produce a completion");
12185
+ if (!completion || !activeAttempt) {
12186
+ if (options.signal?.aborted) {
12187
+ finishReason = "cancelled";
12188
+ break;
12189
+ }
12190
+ throw new AgentError("LLM adapter did not produce a completion");
12191
+ }
11946
12192
  const toolCalls = completion.toolCalls ?? [];
11947
- if (finalTurnGranted && toolCalls.length) {
12193
+ if (completion.finishReason === "length") lengthReached = true;
12194
+ if (options.signal?.aborted && toolCalls.length === 0) {
12195
+ yield activeAttempt.push({ type: "stream_error", error: { name: "AbortError", message: "stream aborted" } });
12196
+ yield* this.settleAttempt(activeAttempt);
12197
+ finishReason = "cancelled";
12198
+ break;
12199
+ }
12200
+ if (finalTurnGranted && toolCalls.length && !options.signal?.aborted) {
12201
+ yield activeAttempt.push({ type: "stream_error", error: { name: "AgentError", message: "maximum turns reached" } });
12202
+ yield* this.settleAttempt(activeAttempt);
11948
12203
  finishReason = "max_turns";
11949
12204
  break;
11950
12205
  }
11951
12206
  const toolResults = [];
11952
12207
  if (toolCalls.length) {
11953
- await session2.append("assistant/message", {
12208
+ yield* this.settleAttempt(activeAttempt, {
11954
12209
  content: completion.content ?? "",
11955
12210
  toolCalls: toolCalls.map((call) => ({
11956
12211
  id: call.id,
@@ -11971,18 +12226,30 @@ var AgentService = class {
11971
12226
  if (options.signal) toolOptions.signal = options.signal;
11972
12227
  const startedAt = Date.now();
11973
12228
  let result;
11974
- try {
11975
- result = await tools2.execute(call.name, call.arguments, toolOptions);
11976
- } catch (error2) {
12229
+ if (options.signal?.aborted) {
11977
12230
  result = {
11978
12231
  ok: false,
11979
12232
  name: call.name,
11980
12233
  callId: call.id,
11981
12234
  input: call.arguments,
11982
- error: toToolError2(error2),
12235
+ error: abortedToolError(options.signal),
11983
12236
  startedAt,
11984
12237
  durationMs: Date.now() - startedAt
11985
12238
  };
12239
+ } else {
12240
+ try {
12241
+ result = await tools2.execute(call.name, call.arguments, toolOptions);
12242
+ } catch (error2) {
12243
+ result = {
12244
+ ok: false,
12245
+ name: call.name,
12246
+ callId: call.id,
12247
+ input: call.arguments,
12248
+ error: toToolError2(error2),
12249
+ startedAt,
12250
+ durationMs: Date.now() - startedAt
12251
+ };
12252
+ }
11986
12253
  }
11987
12254
  toolResults.push(result);
11988
12255
  const toolResultPayload = {
@@ -12024,7 +12291,7 @@ var AgentService = class {
12024
12291
  output = completion.content;
12025
12292
  }
12026
12293
  if (!toolCalls.length) {
12027
- await session2.append("assistant/message", { content: completion.content ?? "" });
12294
+ yield* this.settleAttempt(activeAttempt, { content: completion.content ?? "" });
12028
12295
  }
12029
12296
  await session2.append("step/end", {
12030
12297
  turn,
@@ -12035,19 +12302,19 @@ var AgentService = class {
12035
12302
  currentStepIndex = void 0;
12036
12303
  const nextMessages = this._extendMessages(llmMessages, completion, toolResults);
12037
12304
  const concludesTurn = toolResults.some((result) => result.concludesTurn === true);
12038
- if (concludesTurn) {
12039
- finishReason = "stop";
12040
- break;
12041
- }
12042
- if (toolCalls.length === 0) {
12043
- finishReason = completion.finishReason === "length" ? "length" : completion.finishReason === "error" ? "error" : "stop";
12305
+ let nextStepMessages = await this._claimNextStepMessages();
12306
+ if ((toolCalls.length === 0 || concludesTurn) && nextStepMessages.length === 0) {
12307
+ finishReason = lengthReached ? "length" : completion.finishReason === "error" ? "error" : "stop";
12044
12308
  const keepGoing = await this.ctx.serial("agent/turn-stopping", {
12045
12309
  index,
12046
12310
  turn,
12047
12311
  steps: copySteps(steps),
12048
12312
  finishReason
12049
12313
  });
12050
- if (!keepGoing) break;
12314
+ if (!keepGoing) {
12315
+ nextStepMessages = await this._claimNextStepMessages();
12316
+ if (nextStepMessages.length === 0) break;
12317
+ }
12051
12318
  }
12052
12319
  if (index + 1 >= maxTurns && !finalTurnGranted) {
12053
12320
  finalTurnGranted = true;
@@ -12057,7 +12324,8 @@ var AgentService = class {
12057
12324
  }
12058
12325
  if (toolCalls.length) finishReason = "tool_calls";
12059
12326
  index += 1;
12060
- messages = nextMessages;
12327
+ messages = [...nextMessages, ...nextStepMessages];
12328
+ claimedMessages = nextStepMessages.filter((message2) => message2.role === "user");
12061
12329
  }
12062
12330
  } catch (error2) {
12063
12331
  turnError = error2;
@@ -12143,23 +12411,22 @@ var AgentService = class {
12143
12411
  });
12144
12412
  return compactMessages.map((message2) => copyMessages([message2])[0]);
12145
12413
  }
12146
- async _persistStepInput(session2, request, input) {
12147
- const system = input.find((message2) => message2.role === "system")?.content;
12148
- const tools2 = request.tools.map((tool) => ({
12149
- name: tool.schema.name,
12150
- description: tool.schema.description,
12151
- ...tool.schema.parameters ? { parameters: tool.schema.parameters } : {}
12152
- }));
12153
- const config2 = {
12154
- ...request.options.provider ? { provider: request.options.provider } : {},
12155
- ...request.options.model ? { model: request.options.model } : {},
12156
- ...request.options.temperature !== void 0 ? { temperature: request.options.temperature } : {}
12157
- };
12158
- const nextHeader = {
12159
- ...Object.keys(config2).length ? { config: config2 } : {},
12160
- ...system !== void 0 ? { system } : {},
12161
- ...tools2.length ? { tools: tools2 } : {}
12162
- };
12414
+ async _persistStepInput(session2, request, input, admittedHistory) {
12415
+ const surface = await session2.deriveMessages();
12416
+ const owned = admittedHistory.filter((message2) => !isUserOrSystemMessage(message2));
12417
+ let ownerIndex = 0;
12418
+ for (const message2 of input) {
12419
+ if (isUserOrSystemMessage(message2)) continue;
12420
+ const actual = canonicalMessages([message2]);
12421
+ while (ownerIndex < owned.length && canonicalMessages([owned[ownerIndex]]) !== actual) {
12422
+ ownerIndex += 1;
12423
+ }
12424
+ if (ownerIndex === owned.length) {
12425
+ throw new AgentError("request is not reconstructable from session log: unowned or reordered assistant/tool message");
12426
+ }
12427
+ ownerIndex += 1;
12428
+ }
12429
+ const nextHeader = requestHeaderFor(request, input);
12163
12430
  const previousHeader = session2.requestHeader();
12164
12431
  const isFirstRequest = !this._persistedRequest;
12165
12432
  const isResume = isFirstRequest && previousHeader !== void 0;
@@ -12189,19 +12456,23 @@ var AgentService = class {
12189
12456
  ...request.options.provider ? { provider: request.options.provider } : {},
12190
12457
  ...request.options.model ? { model: request.options.model } : {}
12191
12458
  };
12459
+ if (request.options.contextWindow !== void 0) {
12460
+ nextContext.contextWindow = request.options.contextWindow;
12461
+ }
12192
12462
  const llmService = this.ctx.reflect.get("llm", false);
12193
12463
  const routeCapacity = llmService?.routeCapacity?.();
12194
- if (routeCapacity?.contextWindow !== void 0) {
12464
+ if (nextContext.contextWindow === void 0 && routeCapacity?.contextWindow !== void 0) {
12195
12465
  nextContext.contextWindow = routeCapacity.contextWindow;
12196
12466
  }
12197
12467
  const previousContext = session2.requestContext();
12198
12468
  if (previousContext?.provider !== nextContext.provider || previousContext?.model !== nextContext.model || previousContext?.contextWindow !== nextContext.contextWindow || previousContext === void 0) {
12199
12469
  await session2.append("request/context", nextContext);
12200
12470
  }
12201
- const requested = input.filter(isUserOrSystemMessage);
12202
- const surface = await session2.deriveMessages();
12203
- const appendDurable = async (messages) => {
12204
- for (const message2 of messages) {
12471
+ const isSurfacePrefix = (messages) => canonicalMessages(messages.slice(0, surface.length)) === canonicalMessages(surface);
12472
+ const requested = surface.length > 0 && input[0]?.role === "system" && input[0].content === nextHeader.system && isSurfacePrefix(input.slice(1)) ? input.slice(1) : input;
12473
+ const tail = requested.slice(surface.length);
12474
+ if (isSurfacePrefix(requested) && tail.every(isUserOrSystemMessage)) {
12475
+ for (const message2 of tail) {
12205
12476
  if (message2.role === "user") {
12206
12477
  await session2.append("user/message", {
12207
12478
  content: message2.content,
@@ -12214,45 +12485,37 @@ var AgentService = class {
12214
12485
  });
12215
12486
  }
12216
12487
  }
12217
- };
12218
- if (surface.length === 0) {
12219
- await appendDurable(requested);
12220
12488
  return;
12221
12489
  }
12222
- const durableUsers = surface.filter((message2) => message2.role === "user").length;
12223
- let seenUsers = 0;
12224
- const newTail = [];
12225
- for (const message2 of requested) {
12226
- if (message2.role === "user") seenUsers += 1;
12227
- if (seenUsers <= durableUsers) continue;
12228
- newTail.push(message2);
12229
- }
12230
- await appendDurable(newTail);
12490
+ const nodes = await session2.surfaceEvents();
12491
+ await session2.append("checkpoint", {
12492
+ messages: copyMessages(requested),
12493
+ surfaceOp: { op: "replace", start: nodes[0].seq, end: nodes.at(-1).seq }
12494
+ });
12231
12495
  }
12232
- async _assertReplayable(session2, input) {
12496
+ async _assertReplayable(session2, request, input) {
12233
12497
  const reconstructed = await session2.deriveMessages();
12234
- const canonical = (messages) => messages.filter((message2) => message2.role !== "system").map((message2) => ({
12235
- role: message2.role,
12236
- content: message2.content,
12237
- ...message2.tool_call_id ? { tool_call_id: message2.tool_call_id } : {},
12238
- ...message2.name ? { name: message2.name } : {},
12239
- ...message2.tool_calls?.length ? { tool_calls: message2.tool_calls.map((call) => ({
12240
- id: call.id,
12241
- name: call.name,
12242
- args: JSON.stringify(call.arguments ?? {})
12243
- })) } : {}
12244
- }));
12245
- const actual = canonical(input);
12246
- const replay = canonical(reconstructed);
12247
- if (JSON.stringify(actual) !== JSON.stringify(replay)) {
12498
+ const actual = canonicalMessages(input);
12499
+ const replay = canonicalMessages(reconstructed);
12500
+ const header = session2.requestHeader();
12501
+ const headerOwnsPrefix = input[0]?.role === "system" && input[0].content === header?.system && canonicalMessages(input.slice(1)) === replay;
12502
+ if (actual !== replay && !headerOwnsPrefix) {
12248
12503
  throw new AgentError(
12249
- `request is not reconstructable from session log: actual=${JSON.stringify(actual)} replay=${JSON.stringify(replay)}`
12504
+ `request is not reconstructable from session log: actual=${actual} replay=${replay}`
12250
12505
  );
12251
12506
  }
12507
+ const expected = requestHeaderFor(request, input);
12508
+ if (header?.system !== expected.system || JSON.stringify(header?.tools ?? []) !== JSON.stringify(expected.tools ?? []) || JSON.stringify(header?.config ?? {}) !== JSON.stringify(expected.config ?? {})) {
12509
+ throw new AgentError("request envelope is not reconstructable from session log");
12510
+ }
12511
+ }
12512
+ async _claimNextStepMessages() {
12513
+ const inputs = await this.config.claimNextStep?.() ?? [];
12514
+ return inputs.flatMap((input) => input.messages?.length ? copyMessages(input.messages) : input.text ? [{ role: "user", content: input.text }] : input.context !== void 0 ? [{ role: "user", content: stringify2(input.context) }] : []);
12252
12515
  }
12253
12516
  async _initialMessages(input) {
12254
12517
  const promptService = this.ctx.reflect.get("systemPrompt", false);
12255
- const assembled = promptService ? (await promptService.assemble()).text.trim() : void 0;
12518
+ const assembled = promptService ? (await promptService.assemble({}, this.ctx)).text.trim() : void 0;
12256
12519
  const systemPrompt = assembled || this.inbox.injected().get("agentSystem");
12257
12520
  const systemMessage = typeof systemPrompt === "string" && systemPrompt ? [{ role: "system", content: systemPrompt }] : [];
12258
12521
  if (input.messages?.length) {
@@ -12277,7 +12540,7 @@ var AgentService = class {
12277
12540
  arguments: call.arguments
12278
12541
  }));
12279
12542
  }
12280
- next.push(assistant);
12543
+ if (assistant.content || toolCalls.length) next.push(assistant);
12281
12544
  for (const call of toolCalls) {
12282
12545
  const result = toolResults.find((candidate) => candidate.callId === call.id);
12283
12546
  const content = result ? result.ok ? stringify2(result.output) : `error: ${result.error?.message ?? "unknown"}` : "error: missing tool result";
@@ -12307,7 +12570,8 @@ var AgentService = class {
12307
12570
  return session2;
12308
12571
  }
12309
12572
  _tools() {
12310
- const tools2 = this.ctx.tools;
12573
+ const scoped = this.ctx.reflect.get("tools", false);
12574
+ const tools2 = scoped ?? this.ctx.tools;
12311
12575
  if (!tools2) throw new AgentError("tools service is required");
12312
12576
  return tools2;
12313
12577
  }