@tangle-network/agent-app 0.47.5 → 0.47.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@ export * from './turn-routes';
13
13
  export * from './gateway-adapter';
14
14
  export * from './stale-turn-lock';
15
15
  export * from './sandbox-producer';
16
+ export * from './protected-runtime-producer';
16
17
  export * from './model-failover-stream';
17
18
  export * from './draft-persistence';
18
19
  export * from './detached-turn';
@@ -49,6 +49,9 @@ import "../chunk-KBJL6LIW.js";
49
49
  import "../chunk-TX6S7XXU.js";
50
50
  import "../chunk-UGWQLQDS.js";
51
51
  import "../chunk-S5SRJJQG.js";
52
+ import {
53
+ ProtectedModelSettlementError
54
+ } from "../chunk-KHRIPDW5.js";
52
55
  import "../chunk-JML7WKWU.js";
53
56
  import {
54
57
  assistantRowIdForTurn,
@@ -1037,6 +1040,184 @@ ${diagnostic.userMessage}` : diagnostic.userMessage;
1037
1040
  };
1038
1041
  }
1039
1042
 
1043
+ // src/chat-routes/protected-runtime-producer.ts
1044
+ import { createExecutor, streamAgentTurn } from "@tangle-network/agent-runtime/kernel";
1045
+ import {
1046
+ runProtectedAgentCandidateModelGrant
1047
+ } from "@tangle-network/agent-runtime/candidate-execution";
1048
+ function createProtectedRuntimeChatProducer(options) {
1049
+ if (!Number.isSafeInteger(options.maxToolCalls) || options.maxToolCalls < 0) {
1050
+ throw new Error("maxToolCalls must be a non-negative integer");
1051
+ }
1052
+ options = {
1053
+ ...options,
1054
+ grant: { ...options.grant, resolve: structuredClone(options.grant.resolve), reserve: structuredClone(options.grant.reserve) },
1055
+ tools: options.tools.map((tool) => ({ ...tool, inputSchema: structuredClone(tool.inputSchema) })),
1056
+ priorMessages: options.priorMessages ? structuredClone(options.priorMessages) : void 0
1057
+ };
1058
+ const tools = new Map(options.tools.map((tool) => [tool.name, tool]));
1059
+ if (tools.size !== options.tools.length) throw new Error("Duplicate protected tool name");
1060
+ const profile = structuredClone(options.profile);
1061
+ let toolCalls = 0;
1062
+ let usage = {};
1063
+ let started = false;
1064
+ let model;
1065
+ let budgetEnforced = false;
1066
+ let toolTokens;
1067
+ async function* events() {
1068
+ if (started) throw new Error("A protected chat producer can only execute once");
1069
+ started = true;
1070
+ const controller = new AbortController();
1071
+ options.signal?.throwIfAborted();
1072
+ if (Date.now() >= options.grant.deadlineAtMs) throw new Error("Protected execution deadline expired");
1073
+ const signal = AbortSignal.any([
1074
+ controller.signal,
1075
+ AbortSignal.timeout(Math.max(1, options.grant.deadlineAtMs - Date.now())),
1076
+ ...options.signal ? [options.signal] : []
1077
+ ]);
1078
+ const channel = new TransformStream();
1079
+ const writer = channel.writable.getWriter();
1080
+ const reader = channel.readable.getReader();
1081
+ const emit = (event) => writer.write(event);
1082
+ const execution = runProtectedAgentCandidateModelGrant({
1083
+ ...options.grant,
1084
+ port: {
1085
+ resolve: (input) => options.grant.port.resolve(input),
1086
+ reserveGrant: (input) => options.grant.port.reserveGrant(input),
1087
+ activateGrant: (input) => options.grant.port.activateGrant(input),
1088
+ // Runtime settles failed executions before rethrowing. Retain that receipt too.
1089
+ settleGrant: async (input) => {
1090
+ let auditError;
1091
+ const settlement = await options.grant.port.settleGrant(input).catch((error) => {
1092
+ if (!(error instanceof ProtectedModelSettlementError)) throw error;
1093
+ auditError = error;
1094
+ return error.settlement;
1095
+ });
1096
+ usage = {
1097
+ inputTokens: settlement.calls.reduce((sum, call) => sum + call.accountedInputTokens, 0),
1098
+ outputTokens: settlement.calls.reduce((sum, call) => sum + call.outputTokens, 0),
1099
+ reasoningTokens: settlement.calls.reduce((sum, call) => sum + call.reasoningTokens, 0),
1100
+ costUsd: settlement.calls.reduce((sum, call) => sum + call.costUsdNanos, 0) / 1e9
1101
+ };
1102
+ budgetEnforced = settlement.usageWithinLimits;
1103
+ if (!budgetEnforced) throw new Error("Provider settlement exceeded execution limits");
1104
+ if (auditError) throw auditError;
1105
+ return settlement;
1106
+ }
1107
+ },
1108
+ execute: async ({ activation, resolved }) => {
1109
+ signal.throwIfAborted();
1110
+ if (Date.now() >= options.grant.deadlineAtMs) throw new Error("Protected execution deadline expired");
1111
+ model = resolved.model;
1112
+ if (resolved.provider !== "anthropic" || resolved.reasoningEffort !== "none") {
1113
+ throw new Error("Protected chat currently requires Anthropic without reasoning");
1114
+ }
1115
+ toolTokens = 0;
1116
+ const apiKey = activation.env.OPENAI_API_KEY;
1117
+ const baseUrl = activation.env.OPENAI_BASE_URL;
1118
+ if (!apiKey || !baseUrl) throw new Error("Protected Router activation is missing");
1119
+ const factory = createExecutor({
1120
+ backend: "router-tools",
1121
+ routerBaseUrl: baseUrl,
1122
+ routerKey: apiKey,
1123
+ tools: options.tools.map((tool) => ({
1124
+ type: "function",
1125
+ function: { name: tool.name, description: tool.description, parameters: tool.inputSchema }
1126
+ })),
1127
+ ...options.priorMessages?.length ? { initialMessages: options.priorMessages } : {},
1128
+ executeToolCall: async (name, args) => {
1129
+ signal.throwIfAborted();
1130
+ const tool = tools.get(name);
1131
+ if (!tool) throw new Error("Tool is not authorized for this execution");
1132
+ if (toolCalls >= options.maxToolCalls) {
1133
+ controller.abort(new Error("Execution tool-call limit reached"));
1134
+ signal.throwIfAborted();
1135
+ }
1136
+ toolCalls += 1;
1137
+ const id = `tool-${toolCalls}`;
1138
+ await emit({ type: "tool_call", data: { id, name, arguments: args } });
1139
+ try {
1140
+ const result = await tool.run(args, { signal });
1141
+ await emit({ type: "tool_result", data: { id, name, output: result } });
1142
+ return JSON.stringify(result);
1143
+ } catch (error) {
1144
+ const message = error instanceof Error ? error.message : "Tool failed";
1145
+ await emit({ type: "tool_result", data: { id, name, error: message } });
1146
+ throw error;
1147
+ }
1148
+ }
1149
+ });
1150
+ let completed = false;
1151
+ for await (const event of streamAgentTurn(
1152
+ { kind: "executor", factory, profile },
1153
+ { prompt: options.prompt },
1154
+ {
1155
+ signal,
1156
+ callId: options.grant.reserve.executionId,
1157
+ timeoutMs: Math.max(1, options.grant.deadlineAtMs - Date.now())
1158
+ }
1159
+ )) {
1160
+ if (event.type === "text_delta" || event.type === "reasoning_delta") {
1161
+ await emit({ type: "message.part.updated", data: {
1162
+ part: { id: event.type, type: event.type === "text_delta" ? "text" : "reasoning" },
1163
+ delta: event.text
1164
+ } });
1165
+ }
1166
+ if (event.type === "final") {
1167
+ if (event.status !== "completed") throw new Error(`Protected execution ${event.status}`);
1168
+ completed = true;
1169
+ await emit({ type: "result", data: { finalText: event.text } });
1170
+ }
1171
+ }
1172
+ if (!completed) throw new Error("Protected execution ended without a terminal result");
1173
+ }
1174
+ }).then(async () => {
1175
+ await writer.close();
1176
+ }).catch((error) => writer.abort(error));
1177
+ try {
1178
+ for (; ; ) {
1179
+ const next = await reader.read();
1180
+ if (next.done) break;
1181
+ yield next.value;
1182
+ }
1183
+ await execution;
1184
+ } finally {
1185
+ controller.abort();
1186
+ await reader.cancel().catch(() => void 0);
1187
+ await execution;
1188
+ reader.releaseLock();
1189
+ writer.releaseLock();
1190
+ }
1191
+ }
1192
+ const producer = createSandboxChatProducer({ events: events(), model: profile.model?.default });
1193
+ return {
1194
+ ...producer,
1195
+ stream: (async function* () {
1196
+ let failure;
1197
+ for await (const event of producer.stream) {
1198
+ if (event.type === "error") failure = event;
1199
+ else yield event;
1200
+ }
1201
+ if (usage.inputTokens !== void 0 && usage.outputTokens !== void 0 && usage.costUsd !== void 0) {
1202
+ yield { type: "usage", usage: {
1203
+ promptTokens: usage.inputTokens,
1204
+ completionTokens: usage.outputTokens,
1205
+ providerCostUsd: usage.costUsd,
1206
+ reasoningTokens: usage.reasoningTokens,
1207
+ toolTokens,
1208
+ toolCallCount: toolCalls,
1209
+ budgetEnforced
1210
+ } };
1211
+ }
1212
+ if (failure) yield failure;
1213
+ })(),
1214
+ get model() {
1215
+ return model ?? profile.model?.default;
1216
+ },
1217
+ usage: () => ({ ...usage })
1218
+ };
1219
+ }
1220
+
1040
1221
  // src/chat-routes/detached-turn.ts
1041
1222
  var TERMINAL_ERROR_TYPES = /* @__PURE__ */ new Set(["error", "session.run.failed"]);
1042
1223
  function errorMessageOf(ev) {
@@ -2583,6 +2764,7 @@ export {
2583
2764
  createAtomicAttachmentWriter,
2584
2765
  createAttachmentUploadRoute,
2585
2766
  createChatTurnRoutes,
2767
+ createProtectedRuntimeChatProducer,
2586
2768
  createSandboxChatProducer,
2587
2769
  createSandboxFileIndexRoute,
2588
2770
  createUploadRoute,