@mupt-ai/dari-router 0.1.0 → 0.2.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/README.md +89 -60
- package/dist/anonymous_actions.js +32 -10
- package/dist/anonymous_actions.js.map +1 -1
- package/dist/auto_router.d.ts +4 -1
- package/dist/auto_router.js +3 -3
- package/dist/auto_router.js.map +1 -1
- package/dist/cache_behavior.d.ts +8 -6
- package/dist/cache_behavior.js +60 -30
- package/dist/cache_behavior.js.map +1 -1
- package/dist/cache_scope.d.ts +2 -2
- package/dist/cache_scope.js +23 -18
- package/dist/cache_scope.js.map +1 -1
- package/dist/cost.d.ts +3 -2
- package/dist/cost.js +32 -23
- package/dist/cost.js.map +1 -1
- package/dist/dari_policy.d.ts +8 -2
- package/dist/dari_policy.js +39 -1
- package/dist/dari_policy.js.map +1 -1
- package/dist/eval_score_imputation.d.ts +26 -0
- package/dist/eval_score_imputation.js +121 -0
- package/dist/eval_score_imputation.js.map +1 -0
- package/dist/framework_lifecycle.d.ts +1 -1
- package/dist/framework_lifecycle.js +4 -1
- package/dist/framework_lifecycle.js.map +1 -1
- package/dist/framework_models.js +6 -0
- package/dist/framework_models.js.map +1 -1
- package/dist/framework_router.js +22 -11
- package/dist/framework_router.js.map +1 -1
- package/dist/framework_types.d.ts +9 -5
- package/dist/index.d.ts +9 -35
- package/dist/index.js +6 -37
- package/dist/index.js.map +1 -1
- package/dist/model_ids.js +4 -0
- package/dist/model_ids.js.map +1 -1
- package/dist/pi_runtime.js +19 -11
- package/dist/pi_runtime.js.map +1 -1
- package/dist/pi_types.d.ts +2 -2
- package/{src/index.ts → dist/policy-engine.d.ts} +2 -14
- package/dist/policy-engine.js +25 -0
- package/dist/policy-engine.js.map +1 -0
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.js +3 -3
- package/dist/prompts.js.map +1 -1
- package/dist/protocols.d.ts +5 -0
- package/dist/protocols.js +6 -0
- package/dist/protocols.js.map +1 -0
- package/dist/route.d.ts +3 -4
- package/dist/route.js +17 -10
- package/dist/route.js.map +1 -1
- package/dist/selector_input.d.ts +1 -0
- package/dist/selector_input.js +66 -78
- package/dist/selector_input.js.map +1 -1
- package/dist/selector_parse.d.ts +1 -0
- package/dist/selector_parse.js +2 -1
- package/dist/selector_parse.js.map +1 -1
- package/dist/selector_request.d.ts +1 -0
- package/dist/selector_request.js +1 -0
- package/dist/selector_request.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/examples/auto_router.ts +40 -0
- package/examples/basic_route.ts +130 -0
- package/examples/create_router.ts +114 -0
- package/examples/dari_policy.ts +86 -0
- package/examples/lease_stores.ts +159 -0
- package/examples/managed/README.md +50 -0
- package/examples/managed/byok-router/README.md +13 -0
- package/examples/managed/byok-router/router.yml +13 -0
- package/examples/managed/custom-rules-router/README.md +12 -0
- package/examples/managed/custom-rules-router/router.yml +24 -0
- package/examples/managed/eval-router/README.md +18 -0
- package/examples/managed/eval-router/router.yml +11 -0
- package/examples/managed/slm-router/README.md +12 -0
- package/examples/managed/slm-router/router.yml +8 -0
- package/examples/pi_runtime.ts +55 -0
- package/package.json +26 -7
- package/src/anonymous_actions.ts +0 -434
- package/src/auto_router.ts +0 -95
- package/src/cache_behavior.ts +0 -130
- package/src/cache_scope.ts +0 -58
- package/src/cache_switch_decision.ts +0 -53
- package/src/candidate_pruning.ts +0 -257
- package/src/candidate_resolution.ts +0 -208
- package/src/compatibility.ts +0 -162
- package/src/continuation_state.ts +0 -368
- package/src/cost.ts +0 -455
- package/src/dari_policy.ts +0 -360
- package/src/errors.ts +0 -27
- package/src/fingerprint.ts +0 -148
- package/src/fixed_turn_cost_config.ts +0 -24
- package/src/framework_error.ts +0 -38
- package/src/framework_lifecycle.ts +0 -74
- package/src/framework_models.ts +0 -186
- package/src/framework_router.ts +0 -490
- package/src/framework_streaming.ts +0 -613
- package/src/framework_types.ts +0 -372
- package/src/framework_validation.ts +0 -235
- package/src/json.ts +0 -43
- package/src/model_ids.ts +0 -29
- package/src/pi_context.ts +0 -251
- package/src/pi_options.ts +0 -295
- package/src/pi_output.ts +0 -387
- package/src/pi_reasoning_signature.ts +0 -41
- package/src/pi_runtime.ts +0 -238
- package/src/pi_types.ts +0 -71
- package/src/portable_reasoning_state.ts +0 -129
- package/src/previous_decision.ts +0 -109
- package/src/prompts.ts +0 -69
- package/src/protocol_anthropic.ts +0 -571
- package/src/protocol_openai_chat.ts +0 -645
- package/src/protocol_validation.ts +0 -202
- package/src/route.ts +0 -490
- package/src/routing_history.ts +0 -157
- package/src/selector_input.ts +0 -246
- package/src/selector_parse.ts +0 -138
- package/src/selector_request.ts +0 -130
- package/src/selector_truncation.ts +0 -48
- package/src/selector_wire.ts +0 -167
- package/src/types.ts +0 -231
package/src/pi_output.ts
DELETED
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AssistantMessage,
|
|
3
|
-
AssistantMessageEvent,
|
|
4
|
-
ThinkingContent,
|
|
5
|
-
ToolCall,
|
|
6
|
-
} from "@mupt-ai/pi-ai";
|
|
7
|
-
|
|
8
|
-
import { RouterFrameworkError } from "./framework_error.js";
|
|
9
|
-
import { decodeOpenAIReasoningSignature } from "./pi_reasoning_signature.js";
|
|
10
|
-
import { isRecord } from "./types.js";
|
|
11
|
-
import {
|
|
12
|
-
isOpenAIResponsesApi,
|
|
13
|
-
type ProviderContinuationState,
|
|
14
|
-
type ProviderIdentity,
|
|
15
|
-
} from "./continuation_state.js";
|
|
16
|
-
import {
|
|
17
|
-
isRouterHostedToolCallStatus,
|
|
18
|
-
type RouterCompletion,
|
|
19
|
-
type RouterOutputHostedToolCall,
|
|
20
|
-
type RouterOutputReasoning,
|
|
21
|
-
type RouterStreamEvent,
|
|
22
|
-
} from "./framework_types.js";
|
|
23
|
-
|
|
24
|
-
export function routerCompletion(
|
|
25
|
-
message: AssistantMessage,
|
|
26
|
-
signal: AbortSignal,
|
|
27
|
-
): RouterCompletion {
|
|
28
|
-
assertPiSuccess(message, signal);
|
|
29
|
-
const content: RouterCompletion["content"] = [];
|
|
30
|
-
const source = providerIdentity(message);
|
|
31
|
-
for (const part of message.content) {
|
|
32
|
-
if (part.type === "text") content.push({ type: "text", text: part.text });
|
|
33
|
-
if (part.type === "thinking") content.push(routerReasoning(part, source));
|
|
34
|
-
if (part.type === "toolCall") {
|
|
35
|
-
content.push({
|
|
36
|
-
type: "tool_call",
|
|
37
|
-
id: part.id,
|
|
38
|
-
name: part.name,
|
|
39
|
-
arguments: part.arguments,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
const hostedItem = providerItemRecord(part);
|
|
43
|
-
if (hostedItem !== null) content.push(hostedToolCall(hostedItem, source));
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
...(message.responseId === undefined ? {} : { id: message.responseId }),
|
|
47
|
-
createdAtMs: message.timestamp,
|
|
48
|
-
content,
|
|
49
|
-
finishReason: routerFinishReason(message.stopReason),
|
|
50
|
-
usage: routerUsage(message),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function* routerEvents(
|
|
55
|
-
events: AsyncIterable<AssistantMessageEvent>,
|
|
56
|
-
signal: AbortSignal,
|
|
57
|
-
): AsyncIterable<RouterStreamEvent> {
|
|
58
|
-
const text = new Map<number, string>();
|
|
59
|
-
const thinking = new Map<number, string>();
|
|
60
|
-
const endedThinking = new Set<number>();
|
|
61
|
-
const openTools = new Map<number, { receivedDelta: boolean }>();
|
|
62
|
-
const completedTools = new Map<number, Pick<ToolCall, "id" | "name">>();
|
|
63
|
-
for await (const event of events) {
|
|
64
|
-
if (event.type === "text_start") {
|
|
65
|
-
text.set(event.contentIndex, text.get(event.contentIndex) ?? "");
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (event.type === "text_delta") {
|
|
69
|
-
if (event.delta.length > 0) {
|
|
70
|
-
text.set(event.contentIndex, (text.get(event.contentIndex) ?? "") + event.delta);
|
|
71
|
-
yield { type: "text_delta", index: event.contentIndex, delta: event.delta };
|
|
72
|
-
}
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (event.type === "text_end") {
|
|
76
|
-
const suffix = remainingText(text.get(event.contentIndex) ?? "", event.content);
|
|
77
|
-
if (suffix.length > 0) yield { type: "text_delta", index: event.contentIndex, delta: suffix };
|
|
78
|
-
text.set(event.contentIndex, event.content);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (event.type === "thinking_start") {
|
|
82
|
-
thinking.set(event.contentIndex, "");
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (event.type === "thinking_delta") {
|
|
86
|
-
if (event.delta.length > 0) {
|
|
87
|
-
thinking.set(event.contentIndex, (thinking.get(event.contentIndex) ?? "") + event.delta);
|
|
88
|
-
yield { type: "reasoning_delta", index: event.contentIndex, delta: event.delta };
|
|
89
|
-
}
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (event.type === "thinking_end") {
|
|
93
|
-
const streamed = thinking.get(event.contentIndex) ?? "";
|
|
94
|
-
const suffix = remainingText(streamed, event.content);
|
|
95
|
-
if (suffix.length > 0) yield { type: "reasoning_delta", index: event.contentIndex, delta: suffix };
|
|
96
|
-
thinking.set(event.contentIndex, event.content);
|
|
97
|
-
endedThinking.add(event.contentIndex);
|
|
98
|
-
const part = event.partial.content[event.contentIndex];
|
|
99
|
-
const source = providerIdentity(event.partial);
|
|
100
|
-
if (part?.type === "thinking") {
|
|
101
|
-
const reasoning = routerReasoning(part, source);
|
|
102
|
-
yield {
|
|
103
|
-
type: "reasoning_end",
|
|
104
|
-
index: event.contentIndex,
|
|
105
|
-
...(part.redacted === true ? { redacted: true } : {}),
|
|
106
|
-
source,
|
|
107
|
-
...(reasoning.continuation ? { continuation: reasoning.continuation } : {}),
|
|
108
|
-
};
|
|
109
|
-
} else {
|
|
110
|
-
yield { type: "reasoning_end", index: event.contentIndex, source };
|
|
111
|
-
}
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
if (event.type === "toolcall_start") {
|
|
115
|
-
const call = event.partial.content[event.contentIndex];
|
|
116
|
-
if (call?.type !== "toolCall") throw invalidPiStream("tool-call start is missing its tool call.");
|
|
117
|
-
openTools.set(event.contentIndex, { receivedDelta: false });
|
|
118
|
-
yield {
|
|
119
|
-
type: "tool_call_start",
|
|
120
|
-
index: event.contentIndex,
|
|
121
|
-
id: call.id,
|
|
122
|
-
name: call.name,
|
|
123
|
-
};
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
if (event.type === "toolcall_delta") {
|
|
127
|
-
const state = openTools.get(event.contentIndex);
|
|
128
|
-
if (state === undefined) throw invalidPiStream("tool-call delta arrived before its start.");
|
|
129
|
-
if (event.delta.length > 0) {
|
|
130
|
-
state.receivedDelta = true;
|
|
131
|
-
yield { type: "tool_call_delta", index: event.contentIndex, delta: event.delta };
|
|
132
|
-
}
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
if (event.type === "toolcall_end") {
|
|
136
|
-
const state = openTools.get(event.contentIndex);
|
|
137
|
-
if (state === undefined) {
|
|
138
|
-
yield* completeToolCall(event.contentIndex, event.toolCall);
|
|
139
|
-
completedTools.set(event.contentIndex, event.toolCall);
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
if (!state.receivedDelta) {
|
|
143
|
-
yield {
|
|
144
|
-
type: "tool_call_delta",
|
|
145
|
-
index: event.contentIndex,
|
|
146
|
-
delta: JSON.stringify(event.toolCall.arguments),
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
openTools.delete(event.contentIndex);
|
|
150
|
-
completedTools.set(event.contentIndex, event.toolCall);
|
|
151
|
-
yield { type: "tool_call_end", index: event.contentIndex };
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (event.type === "done") {
|
|
155
|
-
assertPiSuccess(event.message, signal);
|
|
156
|
-
if (event.reason !== "stop" && event.reason !== "length" && event.reason !== "toolUse") {
|
|
157
|
-
throw invalidPiStream(`stream ended with invalid reason '${String(event.reason)}'.`);
|
|
158
|
-
}
|
|
159
|
-
if (openTools.size > 0) throw invalidPiStream("stream finished with open tool calls.");
|
|
160
|
-
yield* reconcileTerminalOutput(event.message, text, thinking, endedThinking, completedTools);
|
|
161
|
-
yield {
|
|
162
|
-
type: "finish",
|
|
163
|
-
finishReason: routerFinishReason(event.reason),
|
|
164
|
-
usage: routerUsage(event.message),
|
|
165
|
-
};
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (event.type === "error") {
|
|
169
|
-
if (event.reason === "aborted") throw piCancellation(event.error, signal);
|
|
170
|
-
throw piProviderError(event.error.errorMessage ?? "Pi provider execution failed.");
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
throw invalidPiStream("stream ended without a terminal event.");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function* reconcileTerminalOutput(
|
|
177
|
-
message: AssistantMessage,
|
|
178
|
-
text: ReadonlyMap<number, string>,
|
|
179
|
-
thinking: ReadonlyMap<number, string>,
|
|
180
|
-
endedThinking: ReadonlySet<number>,
|
|
181
|
-
completedTools: ReadonlyMap<number, Pick<ToolCall, "id" | "name">>,
|
|
182
|
-
): Iterable<RouterStreamEvent> {
|
|
183
|
-
for (const index of text.keys()) {
|
|
184
|
-
if (message.content[index]?.type !== "text") {
|
|
185
|
-
throw invalidPiStream(`terminal output changed text block ${index}.`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
for (const index of thinking.keys()) {
|
|
189
|
-
if (message.content[index]?.type !== "thinking") {
|
|
190
|
-
throw invalidPiStream(`terminal output changed thinking block ${index}.`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
for (const [index, streamed] of completedTools) {
|
|
194
|
-
const terminal = message.content[index];
|
|
195
|
-
if (terminal?.type !== "toolCall" || terminal.id !== streamed.id || terminal.name !== streamed.name) {
|
|
196
|
-
throw invalidPiStream(`terminal output changed tool call ${index}.`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
for (const [index, part] of message.content.entries()) {
|
|
201
|
-
if (part.type === "text") {
|
|
202
|
-
const suffix = remainingText(text.get(index) ?? "", part.text);
|
|
203
|
-
if (suffix.length > 0) yield { type: "text_delta", index, delta: suffix };
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
if (part.type === "thinking") {
|
|
207
|
-
// Started-but-unended thinking blocks reconcile like unended text:
|
|
208
|
-
// emit the remaining delta, then the proper reasoning_end.
|
|
209
|
-
if (!endedThinking.has(index)) {
|
|
210
|
-
yield* completeReasoning(index, part, providerIdentity(message), thinking.get(index) ?? "");
|
|
211
|
-
}
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (part.type === "toolCall") {
|
|
215
|
-
if (!completedTools.has(index)) yield* completeToolCall(index, part);
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
const hostedItem = providerItemRecord(part);
|
|
219
|
-
if (hostedItem !== null) {
|
|
220
|
-
yield { ...hostedToolCall(hostedItem, providerIdentity(message)), index };
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function* completeToolCall(index: number, call: ToolCall): Iterable<RouterStreamEvent> {
|
|
226
|
-
yield { type: "tool_call_start", index, id: call.id, name: call.name };
|
|
227
|
-
yield { type: "tool_call_delta", index, delta: JSON.stringify(call.arguments) };
|
|
228
|
-
yield { type: "tool_call_end", index };
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function* completeReasoning(
|
|
232
|
-
index: number,
|
|
233
|
-
part: ThinkingContent,
|
|
234
|
-
source: ProviderIdentity,
|
|
235
|
-
streamed = "",
|
|
236
|
-
): Iterable<RouterStreamEvent> {
|
|
237
|
-
const suffix = remainingText(streamed, part.thinking);
|
|
238
|
-
if (suffix.length > 0) {
|
|
239
|
-
yield { type: "reasoning_delta", index, delta: suffix };
|
|
240
|
-
}
|
|
241
|
-
const reasoning = routerReasoning(part, source);
|
|
242
|
-
yield {
|
|
243
|
-
type: "reasoning_end",
|
|
244
|
-
index,
|
|
245
|
-
...(part.redacted === true ? { redacted: true } : {}),
|
|
246
|
-
source,
|
|
247
|
-
...(reasoning.continuation ? { continuation: reasoning.continuation } : {}),
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function providerItemRecord(part: unknown): Record<string, unknown> | null {
|
|
252
|
-
if (!isRecord(part) || part.type !== "providerItem") return null;
|
|
253
|
-
if (!isRecord(part.item)) {
|
|
254
|
-
throw unsupportedHostedTool("the provider item has no payload record");
|
|
255
|
-
}
|
|
256
|
-
return part.item;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function hostedToolCall(
|
|
260
|
-
item: Record<string, unknown>,
|
|
261
|
-
source: ProviderIdentity,
|
|
262
|
-
): RouterOutputHostedToolCall {
|
|
263
|
-
if (item.type !== "web_search_call") {
|
|
264
|
-
throw unsupportedHostedTool(
|
|
265
|
-
`only web_search_call items are replayable, got '${String(item.type)}'`,
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
if (typeof item.id !== "string" || item.id.length === 0) {
|
|
269
|
-
throw unsupportedHostedTool("the web_search_call item has no id");
|
|
270
|
-
}
|
|
271
|
-
const status = hostedToolCallStatus(item.status);
|
|
272
|
-
return {
|
|
273
|
-
type: "hosted_tool_call",
|
|
274
|
-
id: item.id,
|
|
275
|
-
tool: "web_search",
|
|
276
|
-
providerType: "web_search_call",
|
|
277
|
-
...(status === undefined ? {} : { status }),
|
|
278
|
-
payload: item,
|
|
279
|
-
source,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function hostedToolCallStatus(value: unknown): RouterOutputHostedToolCall["status"] {
|
|
284
|
-
if (value === undefined || isRouterHostedToolCallStatus(value)) return value;
|
|
285
|
-
throw unsupportedHostedTool(`web_search_call status '${String(value)}' is not replayable`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function unsupportedHostedTool(detail: string): RouterFrameworkError {
|
|
289
|
-
return new RouterFrameworkError(
|
|
290
|
-
"executor",
|
|
291
|
-
`Pi returned a hosted tool item the router cannot replay: ${detail}.`,
|
|
292
|
-
"pi_hosted_tool_unsupported",
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function remainingText(streamed: string, terminal: string): string {
|
|
297
|
-
if (!terminal.startsWith(streamed)) {
|
|
298
|
-
throw invalidPiStream("terminal text does not extend the streamed text.");
|
|
299
|
-
}
|
|
300
|
-
return terminal.slice(streamed.length);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function providerIdentity(
|
|
304
|
-
message: Pick<AssistantMessage, "provider" | "api" | "model">,
|
|
305
|
-
): ProviderIdentity {
|
|
306
|
-
return { provider: message.provider, api: message.api, model: message.model };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function routerReasoning(
|
|
310
|
-
part: ThinkingContent,
|
|
311
|
-
source: ProviderIdentity,
|
|
312
|
-
): RouterOutputReasoning {
|
|
313
|
-
const signature = typeof part.thinkingSignature === "string" && part.thinkingSignature.length > 0
|
|
314
|
-
? part.thinkingSignature
|
|
315
|
-
: undefined;
|
|
316
|
-
let continuation: ProviderContinuationState | undefined;
|
|
317
|
-
if (isOpenAIResponsesApi(source.api) && signature) {
|
|
318
|
-
const native = decodeOpenAIReasoningSignature(signature);
|
|
319
|
-
if (native?.encryptedContent) {
|
|
320
|
-
continuation = {
|
|
321
|
-
kind: "openai_reasoning",
|
|
322
|
-
source,
|
|
323
|
-
encryptedContent: native.encryptedContent,
|
|
324
|
-
...(native.id ? { providerItemId: native.id } : {}),
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
} else if (source.api === "anthropic-messages" && signature) {
|
|
328
|
-
continuation = part.redacted === true
|
|
329
|
-
? { kind: "anthropic_redacted_thinking", source, data: signature }
|
|
330
|
-
: { kind: "anthropic_thinking", source, thinking: part.thinking, signature };
|
|
331
|
-
}
|
|
332
|
-
return {
|
|
333
|
-
type: "reasoning",
|
|
334
|
-
text: part.thinking,
|
|
335
|
-
...(part.redacted === true ? { redacted: true } : {}),
|
|
336
|
-
source,
|
|
337
|
-
...(continuation ? { continuation } : {}),
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function assertPiSuccess(message: AssistantMessage, signal: AbortSignal): void {
|
|
342
|
-
if (message.stopReason === "error") {
|
|
343
|
-
throw piProviderError(message.errorMessage ?? "Pi provider execution failed.");
|
|
344
|
-
}
|
|
345
|
-
if (message.stopReason === "aborted") throw piCancellation(message, signal);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function routerFinishReason(
|
|
349
|
-
reason: "stop" | "length" | "toolUse" | AssistantMessage["stopReason"],
|
|
350
|
-
): RouterCompletion["finishReason"] {
|
|
351
|
-
if (reason === "length") return "length";
|
|
352
|
-
if (reason === "toolUse") return "tool_calls";
|
|
353
|
-
return "stop";
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function routerUsage(
|
|
357
|
-
message: Pick<AssistantMessage, "usage">,
|
|
358
|
-
): NonNullable<RouterCompletion["usage"]> {
|
|
359
|
-
return {
|
|
360
|
-
inputTokens: message.usage.input,
|
|
361
|
-
outputTokens: message.usage.output,
|
|
362
|
-
cacheReadTokens: message.usage.cacheRead,
|
|
363
|
-
cacheWriteTokens: message.usage.cacheWrite,
|
|
364
|
-
totalTokens: message.usage.totalTokens,
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function piCancellation(
|
|
369
|
-
message: Pick<AssistantMessage, "errorMessage">,
|
|
370
|
-
signal: AbortSignal,
|
|
371
|
-
): RouterFrameworkError {
|
|
372
|
-
return new RouterFrameworkError(
|
|
373
|
-
"cancelled",
|
|
374
|
-
typeof signal.reason === "string"
|
|
375
|
-
? signal.reason
|
|
376
|
-
: message.errorMessage ?? "Request cancelled.",
|
|
377
|
-
"request_cancelled",
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function piProviderError(message: string): RouterFrameworkError {
|
|
382
|
-
return new RouterFrameworkError("executor", message, "pi_provider_error");
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function invalidPiStream(message: string): RouterFrameworkError {
|
|
386
|
-
return new RouterFrameworkError("executor", `Pi ${message}`, "pi_stream_invalid");
|
|
387
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { isRecord } from "./types.js";
|
|
2
|
-
|
|
3
|
-
// Wire codec for the JSON blob Pi carries in thinkingSignature on
|
|
4
|
-
// openai-responses APIs: a serialized Responses reasoning item whose
|
|
5
|
-
// encrypted_content holds the replayable state.
|
|
6
|
-
|
|
7
|
-
export function encodeOpenAIReasoningSignature(state: {
|
|
8
|
-
id?: string;
|
|
9
|
-
summary: string[];
|
|
10
|
-
content: string[];
|
|
11
|
-
encryptedContent: string;
|
|
12
|
-
}): string {
|
|
13
|
-
return JSON.stringify({
|
|
14
|
-
type: "reasoning",
|
|
15
|
-
...(state.id ? { id: state.id } : {}),
|
|
16
|
-
summary: state.summary.map((text) => ({ type: "summary_text", text })),
|
|
17
|
-
...(state.content.length
|
|
18
|
-
? { content: state.content.map((text) => ({ type: "reasoning_text", text })) }
|
|
19
|
-
: {}),
|
|
20
|
-
encrypted_content: state.encryptedContent,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function decodeOpenAIReasoningSignature(value: string): {
|
|
25
|
-
id?: string;
|
|
26
|
-
encryptedContent?: string;
|
|
27
|
-
} | null {
|
|
28
|
-
let parsed: unknown;
|
|
29
|
-
try {
|
|
30
|
-
parsed = JSON.parse(value);
|
|
31
|
-
} catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
if (!isRecord(parsed) || parsed.type !== "reasoning") return null;
|
|
35
|
-
return {
|
|
36
|
-
...(typeof parsed.id === "string" && parsed.id.length > 0 ? { id: parsed.id } : {}),
|
|
37
|
-
...(typeof parsed.encrypted_content === "string" && parsed.encrypted_content.length > 0
|
|
38
|
-
? { encryptedContent: parsed.encrypted_content }
|
|
39
|
-
: {}),
|
|
40
|
-
};
|
|
41
|
-
}
|
package/src/pi_runtime.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { RouterFrameworkError } from "./framework_error.js";
|
|
2
|
-
import { nativeModelId, providerForModel } from "./model_ids.js";
|
|
3
|
-
import { piContext } from "./pi_context.js";
|
|
4
|
-
import { piOptions } from "./pi_options.js";
|
|
5
|
-
import { routerCompletion, routerEvents } from "./pi_output.js";
|
|
6
|
-
import { openAIChatRequest } from "./protocol_openai_chat.js";
|
|
7
|
-
import type { ReasoningEffort } from "./types.js";
|
|
8
|
-
import type {
|
|
9
|
-
RouterExecutorResult,
|
|
10
|
-
RouterModel,
|
|
11
|
-
RouterRequest,
|
|
12
|
-
} from "./framework_types.js";
|
|
13
|
-
import type {
|
|
14
|
-
CreatePiRuntimeOptions,
|
|
15
|
-
PiExecution,
|
|
16
|
-
PiModel,
|
|
17
|
-
PiModelRegistry,
|
|
18
|
-
PiRouterModelOptions,
|
|
19
|
-
PiRuntime,
|
|
20
|
-
} from "./pi_types.js";
|
|
21
|
-
|
|
22
|
-
export type {
|
|
23
|
-
CreatePiRuntimeOptions,
|
|
24
|
-
PiApiKey,
|
|
25
|
-
PiCredentialInput,
|
|
26
|
-
PiModelRegistry,
|
|
27
|
-
PiRouterModelOptions,
|
|
28
|
-
PiRuntime,
|
|
29
|
-
} from "./pi_types.js";
|
|
30
|
-
|
|
31
|
-
type PiRuntimeDependencies = {
|
|
32
|
-
registry: PiModelRegistry;
|
|
33
|
-
supportedThinkingLevels(model: PiModel): ReasoningEffort[];
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const STRUCTURED_OUTPUT_APIS = new Set([
|
|
37
|
-
"anthropic-messages",
|
|
38
|
-
"azure-openai-responses",
|
|
39
|
-
"google-generative-ai",
|
|
40
|
-
"google-vertex",
|
|
41
|
-
"mistral-conversations",
|
|
42
|
-
"openai-completions",
|
|
43
|
-
"openai-responses",
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
export async function createPiRuntime(
|
|
47
|
-
options: CreatePiRuntimeOptions,
|
|
48
|
-
): Promise<PiRuntime> {
|
|
49
|
-
validateRuntimeOptions(options);
|
|
50
|
-
const dependencies = await piDependencies(options.registry);
|
|
51
|
-
|
|
52
|
-
const model = <Metadata = unknown>(
|
|
53
|
-
id: string,
|
|
54
|
-
overrides: Partial<PiRouterModelOptions<Metadata>> = {},
|
|
55
|
-
): RouterModel<Metadata> => {
|
|
56
|
-
const provider = modelProvider(id);
|
|
57
|
-
const registered = registeredModel(dependencies.registry, provider, id);
|
|
58
|
-
const reasoningEfforts = overrides.reasoningEfforts === undefined
|
|
59
|
-
? dependencies.supportedThinkingLevels(registered)
|
|
60
|
-
: [...overrides.reasoningEfforts];
|
|
61
|
-
const defaultReasoningEffort = overrides.defaultReasoningEffort
|
|
62
|
-
?? (reasoningEfforts.includes("off") ? "off" : reasoningEfforts[0]);
|
|
63
|
-
return {
|
|
64
|
-
id,
|
|
65
|
-
executor: "pi",
|
|
66
|
-
provider,
|
|
67
|
-
api: registered.api,
|
|
68
|
-
reasoningEfforts,
|
|
69
|
-
...(defaultReasoningEffort === undefined ? {} : { defaultReasoningEffort }),
|
|
70
|
-
capabilities: {
|
|
71
|
-
imageInput: registered.input.includes("image"),
|
|
72
|
-
toolUse: true,
|
|
73
|
-
structuredOutput: STRUCTURED_OUTPUT_APIS.has(registered.api),
|
|
74
|
-
streaming: true,
|
|
75
|
-
...overrides.capabilities,
|
|
76
|
-
},
|
|
77
|
-
...(overrides.metadata === undefined ? {} : { metadata: overrides.metadata }),
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const run = async (execution: PiExecution): Promise<RouterExecutorResult> => {
|
|
82
|
-
const registered = registeredModel(
|
|
83
|
-
dependencies.registry,
|
|
84
|
-
execution.candidate.provider,
|
|
85
|
-
execution.candidate.id,
|
|
86
|
-
);
|
|
87
|
-
if (registered.api !== execution.candidate.api) {
|
|
88
|
-
throw new RouterFrameworkError(
|
|
89
|
-
"configuration",
|
|
90
|
-
`Model '${execution.candidate.id}' declares API '${execution.candidate.api}' but Pi uses '${registered.api}'.`,
|
|
91
|
-
"pi_model_api_mismatch",
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
const context = piContext(execution.request, registered);
|
|
95
|
-
const streamOptions = await piOptions(options, execution, registered);
|
|
96
|
-
if (execution.request.stream) {
|
|
97
|
-
const events = dependencies.registry.streamSimple(registered, context, streamOptions);
|
|
98
|
-
return { type: "stream", events: routerEvents(events, execution.signal) };
|
|
99
|
-
}
|
|
100
|
-
const message = await dependencies.registry.completeSimple(registered, context, streamOptions);
|
|
101
|
-
return { type: "complete", output: routerCompletion(message, execution.signal) };
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
model,
|
|
106
|
-
execute: async (input) => run({
|
|
107
|
-
request: input.request,
|
|
108
|
-
candidate: input.model,
|
|
109
|
-
reasoningEffort: input.decision.reasoningEffort,
|
|
110
|
-
signal: input.signal,
|
|
111
|
-
purpose: "execution",
|
|
112
|
-
}),
|
|
113
|
-
select: async (request, signal = new AbortController().signal) => {
|
|
114
|
-
let normalized: RouterRequest;
|
|
115
|
-
try {
|
|
116
|
-
normalized = openAIChatRequest(request);
|
|
117
|
-
} catch (error) {
|
|
118
|
-
// The selector request is constructed internally; an invalid request
|
|
119
|
-
// here is a router bug, not the end user's fault.
|
|
120
|
-
if (error instanceof RouterFrameworkError && error.kind === "invalid_request") {
|
|
121
|
-
throw new RouterFrameworkError(
|
|
122
|
-
"configuration",
|
|
123
|
-
error.message,
|
|
124
|
-
"pi_selector_request_invalid",
|
|
125
|
-
error.param,
|
|
126
|
-
{ cause: error },
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
const selectorModel = requiredSelectorModel(request.model);
|
|
132
|
-
const candidate = model(selectorModel);
|
|
133
|
-
const result = await run({
|
|
134
|
-
request: { ...normalized, stream: false },
|
|
135
|
-
candidate: {
|
|
136
|
-
id: candidate.id,
|
|
137
|
-
provider: candidate.provider!,
|
|
138
|
-
api: candidate.api!,
|
|
139
|
-
},
|
|
140
|
-
reasoningEffort: normalized.reasoning?.effort ?? candidate.defaultReasoningEffort ?? "off",
|
|
141
|
-
signal,
|
|
142
|
-
purpose: "selector",
|
|
143
|
-
});
|
|
144
|
-
if (result.type !== "complete") {
|
|
145
|
-
throw new RouterFrameworkError(
|
|
146
|
-
"configuration",
|
|
147
|
-
"Pi returned a stream for a non-streaming selector request.",
|
|
148
|
-
"pi_selector_mode_mismatch",
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
return result.output.content
|
|
152
|
-
.filter((item) => item.type === "text")
|
|
153
|
-
.map((item) => item.text)
|
|
154
|
-
.join("");
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function piDependencies(registry: PiModelRegistry | undefined): Promise<PiRuntimeDependencies> {
|
|
160
|
-
const pi = await import("@mupt-ai/pi-ai");
|
|
161
|
-
const resolvedRegistry = registry ?? (await import("@mupt-ai/pi-ai/providers/all")).builtinModels();
|
|
162
|
-
return {
|
|
163
|
-
registry: resolvedRegistry,
|
|
164
|
-
supportedThinkingLevels: (model) => pi.getSupportedThinkingLevels(model) as ReasoningEffort[],
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function validateRuntimeOptions(options: CreatePiRuntimeOptions): void {
|
|
169
|
-
if (!options || (typeof options.apiKey !== "string" && typeof options.apiKey !== "function")) {
|
|
170
|
-
throw new RouterFrameworkError(
|
|
171
|
-
"configuration",
|
|
172
|
-
"createPiRuntime requires an API key or API-key resolver.",
|
|
173
|
-
"pi_api_key_missing",
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
if (typeof options.apiKey === "string" && !options.apiKey.trim()) {
|
|
177
|
-
throw new RouterFrameworkError(
|
|
178
|
-
"configuration",
|
|
179
|
-
"createPiRuntime API keys must be non-empty strings.",
|
|
180
|
-
"pi_api_key_missing",
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
for (const [name, value] of [
|
|
184
|
-
["timeoutMs", options.timeoutMs],
|
|
185
|
-
["maxRetries", options.maxRetries],
|
|
186
|
-
] as const) {
|
|
187
|
-
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
188
|
-
throw new RouterFrameworkError(
|
|
189
|
-
"configuration",
|
|
190
|
-
`createPiRuntime ${name} must be a non-negative integer.`,
|
|
191
|
-
"pi_options_invalid",
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function modelProvider(id: string): string {
|
|
198
|
-
try {
|
|
199
|
-
return providerForModel(id);
|
|
200
|
-
} catch (error) {
|
|
201
|
-
throw new RouterFrameworkError(
|
|
202
|
-
"configuration",
|
|
203
|
-
`Pi model '${id}' must include a provider prefix.`,
|
|
204
|
-
"pi_model_provider_missing",
|
|
205
|
-
undefined,
|
|
206
|
-
{ cause: error },
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function registeredModel(registry: PiModelRegistry, provider: string, id: string): PiModel {
|
|
212
|
-
let nativeId: string;
|
|
213
|
-
try {
|
|
214
|
-
nativeId = nativeModelId(id);
|
|
215
|
-
} catch {
|
|
216
|
-
nativeId = id;
|
|
217
|
-
}
|
|
218
|
-
const registered = registry.getModel(provider, nativeId) ?? registry.getModel(provider, id);
|
|
219
|
-
if (registered === undefined) {
|
|
220
|
-
throw new RouterFrameworkError(
|
|
221
|
-
"configuration",
|
|
222
|
-
`Pi does not know model '${id}' for provider '${provider}'.`,
|
|
223
|
-
"pi_model_not_found",
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
return registered;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function requiredSelectorModel(value: unknown): string {
|
|
230
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
231
|
-
throw new RouterFrameworkError(
|
|
232
|
-
"configuration",
|
|
233
|
-
"The Pi selector request requires a model.",
|
|
234
|
-
"pi_selector_model_missing",
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
return value;
|
|
238
|
-
}
|