@theoremjs/agents 0.3.4-next.407.gd93ec2c → 0.3.4-next.413.gb7a2b75

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.
@@ -152,6 +152,36 @@ function appendPromotedToolMedia(blocks, tool, idPrefix, seenUrls) {
152
152
  });
153
153
  }
154
154
  }
155
+ /**
156
+ * A named inject's user messages as the user's own blocks, where the reply took them in: its
157
+ * text, then a block per file. A message from anyone else (a host's note) is not the user's to show.
158
+ */
159
+ function injectedBlocks(inject, idPrefix) {
160
+ return inject.messages.flatMap((message) => {
161
+ if (message.role !== 'user')
162
+ return [];
163
+ const parts = message.parts ?? [];
164
+ const text = [message.content ?? '', ...parts.map((part) => ('text' in part ? part.text : ''))]
165
+ .join('')
166
+ .trim();
167
+ return [
168
+ ...(text ? [{ id: nextBlockId(idPrefix), kind: 'user-text', text }] : []),
169
+ ...parts.flatMap((part) => 'text' in part
170
+ ? []
171
+ : [
172
+ {
173
+ id: nextBlockId(idPrefix),
174
+ kind: part.type === 'audio' ? 'user-voice' : 'user-attachment',
175
+ name: part.mimeType,
176
+ mimeType: part.mimeType,
177
+ ...('data' in part
178
+ ? { sizeBytes: Math.floor((part.data.length * 3) / 4), data: part.data }
179
+ : { sizeBytes: 0 }),
180
+ },
181
+ ]),
182
+ ];
183
+ });
184
+ }
155
185
  /** Turns a user draft into transcript blocks: a `user-text` block for the trimmed text, then one block per attachment and per voice clip. */
156
186
  function buildUserTurnBlocks(draft, idPrefix = 'user') {
157
187
  const blocks = [];
@@ -185,6 +215,39 @@ function buildUserTurnBlocks(draft, idPrefix = 'user') {
185
215
  }
186
216
  return blocks;
187
217
  }
218
+ /** The block an event adds on its own, beside the blocks before it; `undefined` for one that adds none. */
219
+ function standaloneBlock(event, idPrefix) {
220
+ switch (event.type) {
221
+ case 'media':
222
+ return event.media
223
+ ? {
224
+ id: nextBlockId(idPrefix),
225
+ kind: 'media',
226
+ mimeType: event.media.mimeType,
227
+ data: event.media.data,
228
+ }
229
+ : undefined;
230
+ case 'grounding':
231
+ return { id: nextBlockId(idPrefix), kind: 'grounding', grounding: event.grounding };
232
+ case 'evidence':
233
+ return { id: nextBlockId(idPrefix), kind: 'evidence', evidence: event.evidence };
234
+ case 'error':
235
+ return event.error
236
+ ? { id: nextBlockId(idPrefix), kind: 'error', message: event.error }
237
+ : undefined;
238
+ case 'done':
239
+ return {
240
+ id: nextBlockId(idPrefix),
241
+ kind: 'turn-done',
242
+ stop: event.stop,
243
+ tokens: event.tokens,
244
+ interactionId: event.interactionId,
245
+ compaction: event.compaction !== undefined,
246
+ };
247
+ default:
248
+ return undefined;
249
+ }
250
+ }
188
251
  /** Resets only the turn id sequence, so streaming refolds keep stable `turn-*` keys. */
189
252
  function foldTurnEvents(events, options = {}) {
190
253
  const idPrefix = options.idPrefix ?? 'turn';
@@ -217,33 +280,9 @@ function foldTurnEvents(events, options = {}) {
217
280
  case 'structured':
218
281
  settleStructured(blocks, event.structured, idPrefix);
219
282
  break;
220
- case 'media':
221
- if (event.media) {
222
- blocks.push({
223
- id: nextBlockId(idPrefix),
224
- kind: 'media',
225
- mimeType: event.media.mimeType,
226
- data: event.media.data,
227
- });
228
- }
229
- break;
230
- case 'grounding':
231
- blocks.push({
232
- id: nextBlockId(idPrefix),
233
- kind: 'grounding',
234
- grounding: event.grounding,
235
- });
236
- break;
237
283
  case 'citation':
238
284
  foldCitation(blocks, event, idPrefix);
239
285
  break;
240
- case 'evidence':
241
- blocks.push({
242
- id: nextBlockId(idPrefix),
243
- kind: 'evidence',
244
- evidence: event.evidence,
245
- });
246
- break;
247
286
  case 'guardrail':
248
287
  addGuardrailBlock(blocks, {
249
288
  id: nextBlockId(idPrefix),
@@ -251,27 +290,15 @@ function foldTurnEvents(events, options = {}) {
251
290
  guardrail: event.guardrail,
252
291
  });
253
292
  break;
254
- case 'error':
255
- if (event.error) {
256
- blocks.push({
257
- id: nextBlockId(idPrefix),
258
- kind: 'error',
259
- message: event.error,
260
- });
261
- }
262
- break;
263
- case 'done':
264
- blocks.push({
265
- id: nextBlockId(idPrefix),
266
- kind: 'turn-done',
267
- stop: event.stop,
268
- tokens: event.tokens,
269
- interactionId: event.interactionId,
270
- compaction: event.compaction !== undefined,
271
- });
272
- break;
273
- default:
293
+ case 'stage':
294
+ for (const steer of event.injected ?? [])
295
+ blocks.push(...injectedBlocks(steer, idPrefix));
274
296
  break;
297
+ default: {
298
+ const block = standaloneBlock(event, idPrefix);
299
+ if (block)
300
+ blocks.push(block);
301
+ }
275
302
  }
276
303
  }
277
304
  return blocks;
@@ -165,6 +165,13 @@ function foldAssistantEvents(history, events, gated) {
165
165
  textBuf += event.text;
166
166
  continue;
167
167
  }
168
+ if (event.type === 'stage' && event.injected) {
169
+ // why: A steer is a message in the conversation, after what the reply said and did before it.
170
+ flushStep();
171
+ flushText();
172
+ next = [...next, ...event.injected.flatMap((inject) => inject.messages)];
173
+ continue;
174
+ }
168
175
  if (event.type === 'structured' && event.structured !== undefined) {
169
176
  flushStep();
170
177
  // why: The streamed text is this reply's JSON as the model wrote it; the structured output is the reply as kept.
@@ -12,8 +12,6 @@ export interface ApplyTurnStageArgs extends StageCallBag {
12
12
  /** Another provider step would exceed maxSteps — reject inject. */
13
13
  injectWouldExceedMaxSteps?: boolean;
14
14
  host?: unknown;
15
- /** `before_end`: the answer the model just wrote, which goes into turn history ahead of an inject. */
16
- reply?: readonly TurnHistoryMessage[];
17
15
  /** Where that answer's text begins in the attempt's reply. */
18
16
  replyAt?: number;
19
17
  }
@@ -93,6 +93,7 @@ export async function* applyTurnStage(args) {
93
93
  throwIfAborted(call.signal);
94
94
  const run = runStage({
95
95
  ...call,
96
+ ...(reply ? { reply } : {}),
96
97
  history: state.currentHistory,
97
98
  handlers: onStage ? [onStage] : [],
98
99
  guardrails: profile.guardrails,
@@ -2,7 +2,7 @@ import type { SpanHandle } from '../observability/trace-span.js';
2
2
  import type { StageApplyWarningCode, TurnStage } from './schema.js';
3
3
  import type { TurnStop } from './stop.js';
4
4
  import type { ModelToolResult, ToolFailure, ToolGate } from './tools/types.js';
5
- import type { AwaitingUserInput, StageApplyWarning, TurnEventOf } from './turn-events.js';
5
+ import type { AwaitingUserInput, InjectedMessages, StageApplyWarning, TurnEventOf } from './turn-events.js';
6
6
  import type { Profile, TurnEvent, TurnHistoryMessage } from './types.js';
7
7
  export type { AwaitingUserInput, ToolGate };
8
8
  /**
@@ -37,6 +37,11 @@ export type StageCallBag = {
37
37
  awaiting?: boolean;
38
38
  stop?: TurnStop;
39
39
  gate?: ToolGate;
40
+ /**
41
+ * `before_end` on text: the answer the model just wrote, before any guardrail verdict on it and
42
+ * not yet in `history`. Its last step wrote no text when no message here carries any.
43
+ */
44
+ reply?: readonly TurnHistoryMessage[];
40
45
  };
41
46
  /** What a stage handler is given: the stage, the step, and what has happened so far. */
42
47
  export interface StageContext extends StageCallBag {
@@ -145,9 +150,7 @@ export type StageEventExtra = {
145
150
  awaiting?: boolean;
146
151
  gate?: ToolGate;
147
152
  stop?: TurnStop;
148
- injected?: {
149
- id: string;
150
- }[];
153
+ injected?: InjectedMessages[];
151
154
  released?: number;
152
155
  };
153
156
  /** Build a stream `stage` event. Unknown extra keys are not copied. */
@@ -155,7 +158,7 @@ export declare function stageEventFields(stage: TurnStage, extra?: Partial<Stage
155
158
  export declare function injectMessages(units: readonly InjectUnit[]): TurnHistoryMessage[];
156
159
  /**
157
160
  * The `stage` event recording that `units` landed, naming the ones the host
158
- * named, and the reply text the user has when they dropped a draft (`released`);
161
+ * named with the messages they put in the conversation, and the reply text the user has when they dropped a draft (`released`);
159
162
  * `undefined` when there is neither. Emit it where the messages land, never
160
163
  * before: an inject that never lands is never reported.
161
164
  */
@@ -328,12 +328,12 @@ export function injectMessages(units) {
328
328
  }
329
329
  /**
330
330
  * The `stage` event recording that `units` landed, naming the ones the host
331
- * named, and the reply text the user has when they dropped a draft (`released`);
331
+ * named with the messages they put in the conversation, and the reply text the user has when they dropped a draft (`released`);
332
332
  * `undefined` when there is neither. Emit it where the messages land, never
333
333
  * before: an inject that never lands is never reported.
334
334
  */
335
335
  export function injectedStageEvent(stage, units, extra) {
336
- const injected = units.flatMap((unit) => (unit.id === undefined ? [] : [{ id: unit.id }]));
336
+ const injected = units.flatMap((unit) => unit.id === undefined ? [] : [{ id: unit.id, messages: unit.messages }]);
337
337
  if (injected.length === 0 && extra?.released === undefined)
338
338
  return undefined;
339
339
  return stageEventFields(stage, { ...extra, ...(injected.length > 0 ? { injected } : {}) });
@@ -67,6 +67,14 @@ export interface TurnHistoryMessage {
67
67
  }
68
68
  /** The schema a history message parses against. */
69
69
  export declare const turnHistoryMessageSchema: z.ZodType<TurnHistoryMessage>;
70
+ /**
71
+ * A named inject as it landed: the host's id for it and the messages it added to the conversation,
72
+ * after the `injected` guardrails, so a steer reads as the model read it.
73
+ */
74
+ export interface InjectedMessages {
75
+ id: string;
76
+ messages: TurnHistoryMessage[];
77
+ }
70
78
  /** Why a turn or live utterance ended. */
71
79
  export interface TurnStop {
72
80
  kind: TurnStopKind;
@@ -686,10 +694,8 @@ export type TurnEvent = {
686
694
  stop?: TurnStop;
687
695
  /** Stage result fields the kernel dropped. */
688
696
  stageWarnings?: StageApplyWarning[];
689
- /** The named injects (`StageResult.injectId`) that landed in the conversation at this stage. */
690
- injected?: {
691
- id: string;
692
- }[];
697
+ /** The named injects (`StageResult.injectId`) that landed in the conversation at this stage, each with the messages it added. */
698
+ injected?: InjectedMessages[];
693
699
  /**
694
700
  * An inject at this stage dropped a draft the user never got: of the reply text since the
695
701
  * turn began or its last `attempt`, only the first `released` characters stay the reply.
@@ -461,7 +461,7 @@ const TURN_EVENTS = {
461
461
  stop: turnStop.optional(),
462
462
  stageWarnings: z.array(stageApplyWarning).optional(),
463
463
  injected: z
464
- .array(z.object({ id: z.string().regex(/\S/) }))
464
+ .array(z.object({ id: z.string().regex(/\S/), messages: z.array(turnHistoryMessage) }))
465
465
  .min(1)
466
466
  .optional(),
467
467
  released: z.number().int().nonnegative().optional(),
@@ -5,8 +5,8 @@ import type { StageHandler } from './stages.js';
5
5
  import type { GateDecision } from './tools/gate-answer.js';
6
6
  import type { PageToolExecution, ToolExecutionCoordinator } from './tools/operation.js';
7
7
  import type { AgentCall, AgentCallHook, AgentCallRequest, AgentToolDef, AgentToolInput, AgentToolOutput, HostProfileToolsSpec, InvokeToolRequest, InvokeToolResume, InvokeToolStepCall, InvokeToolStepRequest, LiveProfileToolsSpec, ModelToolResult, PageAnswer, ProfileToolsSpec, RegisteredTool, ToolCallEvent, ToolFailure, ToolGate, ToolLoadContext, ToolPolicy, TurnToolSnapshot, WireFunctionTool } from './tools/types.js';
8
- import type { AttemptReason, CallDone, CompactionFailure, CompactionResult, CompactionSignal, DoneFields, GroundingEvent, InteractionMediaPart, InteractionMediaRefPart, InteractionPart, InteractionTextPart, InvalidField, ModelEvent, ProviderEvidence, RemovedItem, SessionEnded, SessionEvent, SessionEventKind, SessionEventOf, Source, TurnCost, TurnEvent, TurnEventOf, TurnEventType, TurnGroundingCount, TurnHistoryMessage, TurnResponse, TurnTokenSide, TurnTokens } from './turn-events.js';
9
- export type { AgentCall, AgentCallHook, AgentCallRequest, AgentToolDef, AgentToolInput, AgentToolOutput, AttemptReason, CacheMode, CacheTtl, CallDone, CompactionFailure, CompactionMeter, CompactionOutcome, CompactionResult, CompactionSignal, CompactionTiming, ContinueStopKind, DoneFields, FieldMeta, GroundingEvent, HostProfileToolsSpec, InteractionMediaPart, InteractionMediaRefPart, InteractionPart, InteractionTextPart, InvalidField, InvokeToolRequest, InvokeToolResume, InvokeToolStepCall, InvokeToolStepRequest, KeySlot, KeyVault, LiveActivityHandling, LiveEndSensitivity, LiveProfileToolsSpec, LiveStartSensitivity, MediaInputKind, ModelEvent, ProfileToolsSpec, ProfileType, ProviderEvidence, RegisteredTool, RemovedItem, SessionEnded, SessionEvent, SessionEventKind, SessionEventOf, Source, SpeechAudioFormat, StreamMode, SummaryMode, ThinkingLevel, ToolCallEvent, ToolFailure, ToolGate, ToolLoadContext, ToolLoadTier, ToolPolicy, TurnCost, TurnEvent, TurnEventOf, TurnEventType, TurnGroundingCount, TurnHistoryMessage, TurnResponse, TurnStage, TurnStopKind, TurnTokenSide, TurnTokens, TurnToolSnapshot, WireFunctionTool, };
8
+ import type { AttemptReason, CallDone, CompactionFailure, CompactionResult, CompactionSignal, DoneFields, GroundingEvent, InjectedMessages, InteractionMediaPart, InteractionMediaRefPart, InteractionPart, InteractionTextPart, InvalidField, ModelEvent, ProviderEvidence, RemovedItem, SessionEnded, SessionEvent, SessionEventKind, SessionEventOf, Source, TurnCost, TurnEvent, TurnEventOf, TurnEventType, TurnGroundingCount, TurnHistoryMessage, TurnResponse, TurnTokenSide, TurnTokens } from './turn-events.js';
9
+ export type { AgentCall, AgentCallHook, AgentCallRequest, AgentToolDef, AgentToolInput, AgentToolOutput, AttemptReason, CacheMode, CacheTtl, CallDone, CompactionFailure, CompactionMeter, CompactionOutcome, CompactionResult, CompactionSignal, CompactionTiming, ContinueStopKind, DoneFields, FieldMeta, GroundingEvent, HostProfileToolsSpec, InjectedMessages, InteractionMediaPart, InteractionMediaRefPart, InteractionPart, InteractionTextPart, InvalidField, InvokeToolRequest, InvokeToolResume, InvokeToolStepCall, InvokeToolStepRequest, KeySlot, KeyVault, LiveActivityHandling, LiveEndSensitivity, LiveProfileToolsSpec, LiveStartSensitivity, MediaInputKind, ModelEvent, ProfileToolsSpec, ProfileType, ProviderEvidence, RegisteredTool, RemovedItem, SessionEnded, SessionEvent, SessionEventKind, SessionEventOf, Source, SpeechAudioFormat, StreamMode, SummaryMode, ThinkingLevel, ToolCallEvent, ToolFailure, ToolGate, ToolLoadContext, ToolLoadTier, ToolPolicy, TurnCost, TurnEvent, TurnEventOf, TurnEventType, TurnGroundingCount, TurnHistoryMessage, TurnResponse, TurnStage, TurnStopKind, TurnTokenSide, TurnTokens, TurnToolSnapshot, WireFunctionTool, };
10
10
  /** A host-chosen key naming one model binding in a profile's `models`. */
11
11
  export type ModelId = string;
12
12
  /** The id of a provider-side tool such as web search. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theoremjs/agents",
3
- "version": "0.3.4-next.407.gd93ec2c",
3
+ "version": "0.3.4-next.413.gb7a2b75",
4
4
  "description": "A flat TypeScript agent kernel for typed profiles, deterministic turn execution, registered tools, provider adapters, guardrails, and host-injected traces.",
5
5
  "keywords": [
6
6
  "agent",