@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.
- package/dist/chat-routes/index.d.ts +1 -0
- package/dist/chat-routes/index.js +182 -0
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-routes/protected-runtime-producer.d.ts +22 -0
- package/dist/chunk-KHRIPDW5.js +135 -0
- package/dist/chunk-KHRIPDW5.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +6 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/protected-model.d.ts +29 -0
- package/package.json +1 -1
|
@@ -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,
|