@tangle-network/agent-runtime 0.4.3 → 0.5.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/index.js CHANGED
@@ -6,6 +6,24 @@ import {
6
6
  scoreKnowledgeReadiness,
7
7
  userQuestionsForKnowledgeGaps
8
8
  } from "@tangle-network/agent-eval";
9
+ var InMemoryRuntimeSessionStore = class {
10
+ sessions = /* @__PURE__ */ new Map();
11
+ events = /* @__PURE__ */ new Map();
12
+ get(sessionId) {
13
+ return this.sessions.get(sessionId);
14
+ }
15
+ put(session) {
16
+ this.sessions.set(session.id, session);
17
+ }
18
+ appendEvent(sessionId, event) {
19
+ const existing = this.events.get(sessionId) ?? [];
20
+ existing.push(event);
21
+ this.events.set(sessionId, existing);
22
+ }
23
+ listEvents(sessionId) {
24
+ return [...this.events.get(sessionId) ?? []];
25
+ }
26
+ };
9
27
  async function runAgentTask(options) {
10
28
  const task = options.task;
11
29
  await emit(options.onEvent, { type: "task_start", task });
@@ -95,6 +113,95 @@ function summarizeAgentTaskRun(result) {
95
113
  costUsd: result.control.spentCostUsd
96
114
  };
97
115
  }
116
+ async function* runAgentTaskStream(options) {
117
+ const task = options.task;
118
+ const input = { task, ...options.input ?? {} };
119
+ const started = streamEvent({ type: "task_start", task });
120
+ yield started;
121
+ const readinessStart = streamEvent({ type: "readiness_start", task });
122
+ yield readinessStart;
123
+ let knowledge = await buildReadiness(task, options.knowledge);
124
+ const questions = userQuestionsForKnowledgeGaps(knowledge.blockingMissingRequirements);
125
+ const acquisitionPlans = acquisitionPlansForKnowledgeGaps([
126
+ ...knowledge.blockingMissingRequirements,
127
+ ...knowledge.nonBlockingGaps
128
+ ]);
129
+ const preflight = await runKnowledgePreflightStream(task, questions, acquisitionPlans, options.knowledge);
130
+ for (const event of preflight.events) yield event;
131
+ if (options.knowledge?.refreshReadiness && (Object.keys(preflight.userAnswers).length > 0 || preflight.acquiredEvidenceIds.length > 0)) {
132
+ yield streamEvent({ type: "readiness_start", task });
133
+ knowledge = await options.knowledge.refreshReadiness({
134
+ task,
135
+ previous: knowledge,
136
+ userAnswers: preflight.userAnswers,
137
+ acquiredEvidenceIds: preflight.acquiredEvidenceIds
138
+ });
139
+ }
140
+ const decision = decideKnowledgeReadiness(knowledge, { minimumScore: options.minimumReadinessScore });
141
+ yield streamEvent({ type: "readiness_end", task, knowledge, decision });
142
+ if (!decision.passed && decision.status === "blocked") {
143
+ const reason = `knowledge readiness blocked: ${decision.reason}`;
144
+ yield streamEvent({ type: "task_end", task, status: "blocked", reason });
145
+ yield streamEvent({ type: "final", task, status: "blocked", reason });
146
+ return;
147
+ }
148
+ const store = options.sessionStore;
149
+ const existing = options.sessionId ? await store?.get(options.sessionId) : void 0;
150
+ const shouldResume = Boolean(options.resume && existing);
151
+ let session = shouldResume && existing ? await resumeBackendSession(options.backend, existing, input, { task, knowledge, signal: options.signal }) : await startBackendSession(options.backend, input, { task, knowledge, signal: options.signal }, options.sessionId);
152
+ await store?.put(session);
153
+ const sessionEvent = streamEvent({
154
+ type: shouldResume ? "session_resumed" : "session_created",
155
+ task,
156
+ session
157
+ });
158
+ await store?.appendEvent?.(session.id, sessionEvent);
159
+ yield sessionEvent;
160
+ const backendStart = streamEvent({ type: "backend_start", task, session, backend: options.backend.kind });
161
+ await store?.appendEvent?.(session.id, backendStart);
162
+ yield backendStart;
163
+ let finalText = "";
164
+ try {
165
+ for await (const rawEvent of options.backend.stream(input, { task, knowledge, session, signal: options.signal })) {
166
+ const event = normalizeBackendStreamEvent(rawEvent, task, session);
167
+ if (event.type === "text_delta") finalText += event.text;
168
+ await store?.appendEvent?.(session.id, event);
169
+ yield event;
170
+ }
171
+ const completedStatus = "completed";
172
+ session = touchSession({ ...session, status: completedStatus });
173
+ await store?.put(session);
174
+ const backendEnd = streamEvent({ type: "backend_end", task, session, backend: options.backend.kind });
175
+ await store?.appendEvent?.(session.id, backendEnd);
176
+ yield backendEnd;
177
+ const reason = "backend completed";
178
+ const taskEnd = streamEvent({ type: "task_end", task, status: completedStatus, reason });
179
+ await store?.appendEvent?.(session.id, taskEnd);
180
+ yield taskEnd;
181
+ const final = streamEvent({ type: "final", task, session, status: completedStatus, reason, text: finalText || void 0 });
182
+ await store?.appendEvent?.(session.id, final);
183
+ yield final;
184
+ } catch (err) {
185
+ const message = err instanceof Error ? err.message : String(err);
186
+ session = touchSession({ ...session, status: options.signal?.aborted ? "aborted" : "failed" });
187
+ await store?.put(session);
188
+ const backendError = streamEvent({
189
+ type: "backend_error",
190
+ task,
191
+ session,
192
+ backend: options.backend.kind,
193
+ message,
194
+ recoverable: !options.signal?.aborted
195
+ });
196
+ await store?.appendEvent?.(session.id, backendError);
197
+ yield backendError;
198
+ const status = options.signal?.aborted ? "aborted" : "failed";
199
+ const taskEnd = streamEvent({ type: "task_end", task, status, reason: message });
200
+ await store?.appendEvent?.(session.id, taskEnd);
201
+ yield taskEnd;
202
+ yield streamEvent({ type: "final", task, session, status, reason: message, text: finalText || void 0 });
203
+ }
204
+ }
98
205
  function decideKnowledgeReadiness(report, options = {}) {
99
206
  const minimumScore = options.minimumScore ?? 0.7;
100
207
  const blockingGapIds = report.blockingMissingRequirements.map((requirement) => requirement.id);
@@ -189,6 +296,98 @@ function sanitizeAgentRuntimeEvent(event, options = {}) {
189
296
  }
190
297
  return { ...base, status: event.status, reason: event.reason };
191
298
  }
299
+ function sanitizeRuntimeStreamEvent(event, options = {}) {
300
+ const withTask = "task" in event && event.task ? { task: sanitizeTask(event.task, options) } : {};
301
+ const withSession = "session" in event && event.session ? { session: sanitizeRuntimeSession(event.session, options) } : {};
302
+ if (event.type === "readiness_end") {
303
+ return {
304
+ type: event.type,
305
+ ...withTask,
306
+ timestamp: event.timestamp,
307
+ decision: event.decision,
308
+ knowledge: sanitizeKnowledgeReadinessReport(event.knowledge, options)
309
+ };
310
+ }
311
+ if (event.type === "questions_start") {
312
+ return { type: event.type, ...withTask, timestamp: event.timestamp, questions: event.questions.map((question) => sanitizeQuestion(question, options)) };
313
+ }
314
+ if (event.type === "questions_end") {
315
+ return {
316
+ type: event.type,
317
+ ...withTask,
318
+ timestamp: event.timestamp,
319
+ questions: event.questions.map((question) => sanitizeQuestion(question, options)),
320
+ userAnswers: options.includeUserAnswers ? event.userAnswers : redactRecord(event.userAnswers)
321
+ };
322
+ }
323
+ if (event.type === "acquisition_start") {
324
+ return { type: event.type, ...withTask, timestamp: event.timestamp, acquisitionPlans: event.acquisitionPlans.map(sanitizeAcquisitionPlan) };
325
+ }
326
+ if (event.type === "acquisition_end") {
327
+ return {
328
+ type: event.type,
329
+ ...withTask,
330
+ timestamp: event.timestamp,
331
+ acquisitionPlans: event.acquisitionPlans.map(sanitizeAcquisitionPlan),
332
+ acquiredEvidenceCount: event.acquiredEvidenceIds.length,
333
+ acquiredEvidenceIds: options.includeEvidenceIds ? event.acquiredEvidenceIds : void 0
334
+ };
335
+ }
336
+ if (event.type === "tool_call") {
337
+ return {
338
+ type: event.type,
339
+ ...withTask,
340
+ ...withSession,
341
+ timestamp: event.timestamp,
342
+ toolName: event.toolName,
343
+ toolCallId: event.toolCallId,
344
+ args: options.includeControlPayloads ? event.args : void 0
345
+ };
346
+ }
347
+ if (event.type === "tool_result") {
348
+ return {
349
+ type: event.type,
350
+ ...withTask,
351
+ ...withSession,
352
+ timestamp: event.timestamp,
353
+ toolName: event.toolName,
354
+ toolCallId: event.toolCallId,
355
+ result: options.includeControlPayloads ? event.result : void 0
356
+ };
357
+ }
358
+ if (event.type === "artifact") {
359
+ return {
360
+ type: event.type,
361
+ ...withTask,
362
+ ...withSession,
363
+ timestamp: event.timestamp,
364
+ artifactId: event.artifactId,
365
+ name: event.name,
366
+ mimeType: event.mimeType,
367
+ uri: options.includeEvidenceIds ? event.uri : void 0,
368
+ metadata: options.includeMetadata ? event.metadata : void 0
369
+ };
370
+ }
371
+ if (event.type === "final") {
372
+ return {
373
+ type: event.type,
374
+ ...withTask,
375
+ ...withSession,
376
+ timestamp: event.timestamp,
377
+ status: event.status,
378
+ reason: event.reason,
379
+ text: options.includeControlPayloads ? event.text : void 0,
380
+ metadata: options.includeMetadata ? event.metadata : void 0
381
+ };
382
+ }
383
+ return {
384
+ type: event.type,
385
+ ...withTask,
386
+ ...withSession,
387
+ timestamp: "timestamp" in event ? event.timestamp : void 0,
388
+ ...pickPublicStreamFields(event)
389
+ };
390
+ }
192
391
  function createRuntimeEventCollector(options = {}) {
193
392
  const events = [];
194
393
  return {
@@ -220,6 +419,93 @@ function readinessServerSentEvent(report, options = {}) {
220
419
  readiness: sanitizeKnowledgeReadinessReport(report, telemetryOptions)
221
420
  }, { event, id, retry });
222
421
  }
422
+ function runtimeStreamServerSentEvent(event, options = {}) {
423
+ const { event: sseEvent, id, retry, ...telemetryOptions } = options;
424
+ return encodeServerSentEvent(sanitizeRuntimeStreamEvent(event, telemetryOptions), { event: sseEvent, id, retry });
425
+ }
426
+ function createIterableBackend(options) {
427
+ return options;
428
+ }
429
+ function createSandboxPromptBackend(options) {
430
+ return {
431
+ kind: options.kind ?? "sandbox",
432
+ async start(input, context) {
433
+ const box = await options.getBox(input, context);
434
+ return newRuntimeSession(options.kind ?? "sandbox", options.getSessionId?.(box, input) ?? context.requestedSessionId, {
435
+ resumable: true
436
+ });
437
+ },
438
+ resume(session) {
439
+ return touchSession({ ...session, status: "active" });
440
+ },
441
+ async *stream(input, context) {
442
+ const box = await options.getBox(input, context);
443
+ const message = input.message ?? input.messages?.at(-1)?.content ?? context.task.intent;
444
+ for await (const event of options.streamPrompt(box, message, context)) {
445
+ const mapped = options.mapEvent?.(event, context) ?? mapCommonBackendEvent(event, context);
446
+ if (mapped) yield mapped;
447
+ }
448
+ }
449
+ };
450
+ }
451
+ function createCliBridgeBackend(options) {
452
+ const fetcher = options.fetchImpl ?? fetch;
453
+ return {
454
+ kind: options.kind ?? "cli-bridge",
455
+ start(_input, context) {
456
+ return newRuntimeSession(options.kind ?? "cli-bridge", context.requestedSessionId, { resumable: true });
457
+ },
458
+ resume(session) {
459
+ return touchSession({ ...session, status: "active" });
460
+ },
461
+ async *stream(input, context) {
462
+ const response = await fetcher(options.url, {
463
+ method: "POST",
464
+ headers: {
465
+ "Content-Type": "application/json",
466
+ ...options.bearer ? { Authorization: `Bearer ${options.bearer}` } : {}
467
+ },
468
+ body: JSON.stringify({
469
+ sessionId: context.session.id,
470
+ resumeToken: context.session.resumeToken,
471
+ task: input.task,
472
+ message: input.message,
473
+ messages: input.messages,
474
+ inputs: input.inputs
475
+ }),
476
+ signal: context.signal
477
+ });
478
+ if (!response.ok) throw new Error(`cli bridge returned ${response.status}`);
479
+ yield* streamResponseEvents(response, context);
480
+ }
481
+ };
482
+ }
483
+ function createOpenAICompatibleBackend(options) {
484
+ const fetcher = options.fetchImpl ?? fetch;
485
+ return {
486
+ kind: options.kind ?? "tcloud",
487
+ start(_input, context) {
488
+ return newRuntimeSession(options.kind ?? "tcloud", context.requestedSessionId);
489
+ },
490
+ async *stream(input, context) {
491
+ const response = await fetcher(`${options.baseUrl.replace(/\/$/, "")}/chat/completions`, {
492
+ method: "POST",
493
+ headers: {
494
+ Authorization: `Bearer ${options.apiKey}`,
495
+ "Content-Type": "application/json"
496
+ },
497
+ body: JSON.stringify({
498
+ model: options.model,
499
+ stream: true,
500
+ messages: input.messages ?? [{ role: "user", content: input.message ?? context.task.intent }]
501
+ }),
502
+ signal: context.signal
503
+ });
504
+ if (!response.ok) throw new Error(`chat backend returned ${response.status}`);
505
+ yield* streamResponseEvents(response, context);
506
+ }
507
+ };
508
+ }
223
509
  async function runKnowledgePreflight(task, questions, acquisitionPlans, provider, onEvent) {
224
510
  let userAnswers = {};
225
511
  let acquiredEvidenceIds = [];
@@ -235,6 +521,22 @@ async function runKnowledgePreflight(task, questions, acquisitionPlans, provider
235
521
  }
236
522
  return { userAnswers, acquiredEvidenceIds };
237
523
  }
524
+ async function runKnowledgePreflightStream(task, questions, acquisitionPlans, provider) {
525
+ const events = [];
526
+ let userAnswers = {};
527
+ let acquiredEvidenceIds = [];
528
+ if (questions.length > 0 && provider?.answerQuestions) {
529
+ events.push(streamEvent({ type: "questions_start", task, questions }));
530
+ userAnswers = await provider.answerQuestions(questions, task);
531
+ events.push(streamEvent({ type: "questions_end", task, questions, userAnswers }));
532
+ }
533
+ if (acquisitionPlans.length > 0 && provider?.executeAcquisitionPlans) {
534
+ events.push(streamEvent({ type: "acquisition_start", task, acquisitionPlans }));
535
+ acquiredEvidenceIds = await provider.executeAcquisitionPlans(acquisitionPlans, task);
536
+ events.push(streamEvent({ type: "acquisition_end", task, acquisitionPlans, acquiredEvidenceIds }));
537
+ }
538
+ return { userAnswers, acquiredEvidenceIds, events };
539
+ }
238
540
  function sanitizeTask(task, options) {
239
541
  return {
240
542
  id: task.id,
@@ -247,6 +549,17 @@ function sanitizeTask(task, options) {
247
549
  metadata: options.includeMetadata ? task.metadata : task.metadata ? "[redacted]" : void 0
248
550
  };
249
551
  }
552
+ function sanitizeRuntimeSession(session, options) {
553
+ return {
554
+ id: session.id,
555
+ backend: session.backend,
556
+ status: session.status,
557
+ hasResumeToken: Boolean(session.resumeToken),
558
+ createdAt: session.createdAt,
559
+ updatedAt: session.updatedAt,
560
+ metadata: options.includeMetadata ? session.metadata : session.metadata ? "[redacted]" : void 0
561
+ };
562
+ }
250
563
  function sanitizeKnowledgeRequirement(requirement, options) {
251
564
  const includeDescription = options.includeRequirementDescriptions && requirement.sensitivity !== "secret";
252
565
  return {
@@ -337,6 +650,135 @@ function redactRecord(record) {
337
650
  function stripNewlines(value) {
338
651
  return value.replace(/[\r\n]/g, " ");
339
652
  }
653
+ function timestamp() {
654
+ return (/* @__PURE__ */ new Date()).toISOString();
655
+ }
656
+ function streamEvent(event) {
657
+ return { ...event, timestamp: timestamp() };
658
+ }
659
+ function newRuntimeSession(backend, requestedId, metadata) {
660
+ const now = timestamp();
661
+ return {
662
+ id: requestedId || crypto.randomUUID(),
663
+ backend,
664
+ status: "active",
665
+ createdAt: now,
666
+ updatedAt: now,
667
+ metadata
668
+ };
669
+ }
670
+ function touchSession(session) {
671
+ return { ...session, updatedAt: timestamp() };
672
+ }
673
+ async function startBackendSession(backend, input, context, requestedSessionId) {
674
+ if (backend.start) return backend.start(input, { ...context, requestedSessionId });
675
+ return newRuntimeSession(backend.kind, requestedSessionId);
676
+ }
677
+ async function resumeBackendSession(backend, session, input, context) {
678
+ if (session.backend !== backend.kind) {
679
+ throw new Error(`Cannot resume ${session.backend} session with ${backend.kind} backend`);
680
+ }
681
+ if (backend.resume) return backend.resume(session, input, context);
682
+ return touchSession({ ...session, status: "active" });
683
+ }
684
+ function normalizeBackendStreamEvent(event, task, session) {
685
+ if ("task" in event && event.task && "session" in event && event.session && "timestamp" in event && event.timestamp) return event;
686
+ return {
687
+ ...event,
688
+ task: "task" in event && event.task ? event.task : task,
689
+ session: "session" in event && event.session ? event.session : session,
690
+ timestamp: "timestamp" in event && event.timestamp ? event.timestamp : timestamp()
691
+ };
692
+ }
693
+ function pickPublicStreamFields(event) {
694
+ if (event.type === "session_created" || event.type === "session_resumed") return {};
695
+ if (event.type === "backend_start" || event.type === "backend_end") return { backend: event.backend };
696
+ if (event.type === "backend_error") return { backend: event.backend, message: event.message, recoverable: event.recoverable };
697
+ if (event.type === "task_end") return { status: event.status, reason: event.reason };
698
+ if (event.type === "text_delta" || event.type === "reasoning_delta") return { text: event.text };
699
+ return {};
700
+ }
701
+ function mapCommonBackendEvent(event, context) {
702
+ if (!event || typeof event !== "object") return void 0;
703
+ const record = event;
704
+ const type = String(record.type ?? "");
705
+ const data = record.data && typeof record.data === "object" ? record.data : record;
706
+ if (type === "message.part.updated" || type === "text_delta" || type === "delta") {
707
+ const text = stringValue(data.text) ?? stringValue(data.delta) ?? stringValue(record.text);
708
+ return text ? { type: "text_delta", task: context.task, session: context.session, text, timestamp: timestamp() } : void 0;
709
+ }
710
+ if (type === "reasoning_delta") {
711
+ const text = stringValue(data.text) ?? stringValue(record.text);
712
+ return text ? { type: "reasoning_delta", task: context.task, session: context.session, text, timestamp: timestamp() } : void 0;
713
+ }
714
+ if (type === "tool_call") {
715
+ return {
716
+ type: "tool_call",
717
+ task: context.task,
718
+ session: context.session,
719
+ toolName: stringValue(data.name) ?? stringValue(record.toolName) ?? "tool",
720
+ toolCallId: stringValue(data.id) ?? stringValue(record.toolCallId),
721
+ args: data.args ?? data.input ?? record.args,
722
+ timestamp: timestamp()
723
+ };
724
+ }
725
+ if (type === "tool_result") {
726
+ return {
727
+ type: "tool_result",
728
+ task: context.task,
729
+ session: context.session,
730
+ toolName: stringValue(data.name) ?? stringValue(record.toolName) ?? "tool",
731
+ toolCallId: stringValue(data.id) ?? stringValue(record.toolCallId),
732
+ result: data.result ?? data.output ?? record.result,
733
+ timestamp: timestamp()
734
+ };
735
+ }
736
+ if (type === "result" || type === "final") {
737
+ const text = stringValue(data.finalText) ?? stringValue(data.text) ?? stringValue(record.text);
738
+ return text ? { type: "text_delta", task: context.task, session: context.session, text, timestamp: timestamp() } : void 0;
739
+ }
740
+ return void 0;
741
+ }
742
+ async function* streamResponseEvents(response, context) {
743
+ const body = response.body;
744
+ if (!body) return;
745
+ const reader = body.getReader();
746
+ const decoder = new TextDecoder();
747
+ let buffer = "";
748
+ for (; ; ) {
749
+ const { done, value } = await reader.read();
750
+ if (done) break;
751
+ buffer += decoder.decode(value, { stream: true });
752
+ const chunks = buffer.split(/\n\n/);
753
+ buffer = chunks.pop() ?? "";
754
+ for (const chunk of chunks) {
755
+ const event = parseStreamChunk(chunk, context);
756
+ if (event) yield event;
757
+ }
758
+ }
759
+ if (buffer.trim()) {
760
+ const event = parseStreamChunk(buffer, context);
761
+ if (event) yield event;
762
+ }
763
+ }
764
+ function parseStreamChunk(chunk, context) {
765
+ const data = chunk.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
766
+ if (!data || data === "[DONE]") return void 0;
767
+ try {
768
+ const parsed = JSON.parse(data);
769
+ const choice = Array.isArray(parsed.choices) ? parsed.choices[0] : void 0;
770
+ const delta = choice?.delta;
771
+ const message = choice?.message;
772
+ const text = stringValue(delta?.content) ?? stringValue(message?.content) ?? stringValue(parsed.text);
773
+ if (text) return { type: "text_delta", task: context.task, session: context.session, text, timestamp: timestamp() };
774
+ return mapCommonBackendEvent(parsed, context);
775
+ } catch {
776
+ return { type: "text_delta", task: context.task, session: context.session, text: data, timestamp: timestamp() };
777
+ }
778
+ }
779
+ function stringValue(value) {
780
+ return typeof value === "string" && value.length > 0 ? value : void 0;
781
+ }
340
782
  function buildReadiness(task, provider) {
341
783
  if (provider?.buildReadiness) return provider.buildReadiness(task);
342
784
  return scoreKnowledgeReadiness({
@@ -373,13 +815,21 @@ function toAgentContext(task, knowledge, ctx) {
373
815
  };
374
816
  }
375
817
  export {
818
+ InMemoryRuntimeSessionStore,
819
+ createCliBridgeBackend,
820
+ createIterableBackend,
821
+ createOpenAICompatibleBackend,
376
822
  createRuntimeEventCollector,
823
+ createSandboxPromptBackend,
377
824
  decideKnowledgeReadiness,
378
825
  encodeServerSentEvent,
379
826
  readinessServerSentEvent,
380
827
  runAgentTask,
828
+ runAgentTaskStream,
829
+ runtimeStreamServerSentEvent,
381
830
  sanitizeAgentRuntimeEvent,
382
831
  sanitizeKnowledgeReadinessReport,
832
+ sanitizeRuntimeStreamEvent,
383
833
  summarizeAgentTaskRun
384
834
  };
385
835
  //# sourceMappingURL=index.js.map