@springbrand/agent-runtime 0.3.0-alpha.20 → 0.3.0-alpha.22
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/package.json +5 -5
- package/src/adapter/cloudflare/agent.ts +79 -80
- package/src/adapter/cloudflare/define-agent.ts +220 -93
- package/src/adapter/cloudflare/schema.ts +6 -2
- package/src/config/activation.ts +13 -15
- package/src/config/harness-runtime.ts +26 -36
- package/src/config/index.ts +1 -1
- package/src/config/{pinning.ts → prepared-runtime.ts} +20 -118
- package/src/config/runtime-config.ts +2 -1
- package/src/conversation/message/contract.ts +16 -4
- package/src/conversation/message/conversion.ts +43 -2
- package/src/harness/session-harness.ts +21 -6
- package/src/submission/admission.ts +8 -29
- package/src/submission/lifecycle.ts +38 -16
- package/src/submission/repository.ts +39 -30
- package/src/telemetry/contract.ts +0 -1
- package/src/telemetry/coordinator.ts +13 -3
- package/src/telemetry/runtime-telemetry.ts +0 -4
- package/src/tools/assistant/ask-user.ts +3 -1
- package/src/tools/extension/index.ts +2 -2
- package/src/tools/web/search/api.ts +1 -1
|
@@ -46,8 +46,6 @@ export interface StoredSubmission {
|
|
|
46
46
|
error: string | null;
|
|
47
47
|
createdAt: number;
|
|
48
48
|
completedAt: number | null;
|
|
49
|
-
assemblyRevision: string;
|
|
50
|
-
assemblyDescriptor: string;
|
|
51
49
|
runtimeKey: string;
|
|
52
50
|
assistantMessageId: string;
|
|
53
51
|
abortReason: string | null;
|
|
@@ -63,6 +61,17 @@ export interface StoredSubmission {
|
|
|
63
61
|
retryable?: boolean;
|
|
64
62
|
}
|
|
65
63
|
|
|
64
|
+
/** Saved key and whether its Submission can still execute. */
|
|
65
|
+
export interface StoredRuntimeKey {
|
|
66
|
+
readonly runtimeKey: string;
|
|
67
|
+
readonly unfinished: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface RuntimeKeyRewrite {
|
|
71
|
+
readonly from: string;
|
|
72
|
+
readonly to: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
/** Reuse durable stop intent when transferring work out of the source Submission. */
|
|
67
76
|
export function submissionAbortReason(submission: StoredSubmission | null): string | undefined {
|
|
68
77
|
return submission?.abortReason ||
|
|
@@ -74,8 +83,6 @@ export interface NewSubmission {
|
|
|
74
83
|
requestId: string;
|
|
75
84
|
idempotencyKey: string | null;
|
|
76
85
|
createdAt: number;
|
|
77
|
-
assemblyRevision: string;
|
|
78
|
-
assemblyDescriptor: string;
|
|
79
86
|
runtimeKey?: string;
|
|
80
87
|
assistantMessageId: string;
|
|
81
88
|
queuedInputJson?: string | null;
|
|
@@ -97,8 +104,6 @@ type SubmissionRow = {
|
|
|
97
104
|
error: string | null;
|
|
98
105
|
created_at: number;
|
|
99
106
|
completed_at: number | null;
|
|
100
|
-
assembly_revision: string;
|
|
101
|
-
assembly_descriptor: string;
|
|
102
107
|
runtime_key: string;
|
|
103
108
|
assistant_message_id: string;
|
|
104
109
|
abort_reason: string | null;
|
|
@@ -129,8 +134,6 @@ function mapRow(row: SubmissionRow): StoredSubmission {
|
|
|
129
134
|
error: row.error,
|
|
130
135
|
createdAt: row.created_at,
|
|
131
136
|
completedAt: row.completed_at,
|
|
132
|
-
assemblyRevision: row.assembly_revision,
|
|
133
|
-
assemblyDescriptor: row.assembly_descriptor,
|
|
134
137
|
runtimeKey: row.runtime_key,
|
|
135
138
|
assistantMessageId: row.assistant_message_id,
|
|
136
139
|
abortReason: row.abort_reason,
|
|
@@ -166,14 +169,36 @@ export class SubmissionRepository {
|
|
|
166
169
|
return this.transact(run);
|
|
167
170
|
}
|
|
168
171
|
|
|
172
|
+
/** Return saved identities for a one-time Host-owned key migration. */
|
|
173
|
+
listRuntimeKeys(): StoredRuntimeKey[] {
|
|
174
|
+
return this.sql<{ runtime_key: string; unfinished: number }>`
|
|
175
|
+
SELECT runtime_key,
|
|
176
|
+
MAX(CASE WHEN status IN ('pending', 'running') THEN 1 ELSE 0 END) AS unfinished
|
|
177
|
+
FROM pi_submissions
|
|
178
|
+
WHERE runtime_key <> ''
|
|
179
|
+
GROUP BY runtime_key
|
|
180
|
+
ORDER BY runtime_key
|
|
181
|
+
`.map((row) => ({ runtimeKey: row.runtime_key, unfinished: row.unfinished === 1 }));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Rewrite each saved identity atomically after the Host validates active work. */
|
|
185
|
+
rewriteRuntimeKeys(rewrites: readonly RuntimeKeyRewrite[]): void {
|
|
186
|
+
this.transact(() => {
|
|
187
|
+
for (const { from, to } of rewrites) {
|
|
188
|
+
if (from === to) continue;
|
|
189
|
+
this.sql`UPDATE pi_submissions SET runtime_key = ${to} WHERE runtime_key = ${from}`;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
169
194
|
// 按 submissionId 读取一条完整 Submission,不存在时返回 null。
|
|
170
195
|
// Runtime 和 SubmissionLifecycle 在准入、执行、取消、结束与恢复时调用,调用方必须处理 null。
|
|
171
196
|
// 所有列在这里一次投影并统一映射,不能让不同调用方各自拼出不完整的 Submission。
|
|
172
197
|
find(id: string): StoredSubmission | null {
|
|
173
198
|
const row = this.sql<SubmissionRow>`
|
|
174
199
|
SELECT submission_id, request_id, idempotency_key, status, accepted,
|
|
175
|
-
error, created_at, completed_at,
|
|
176
|
-
|
|
200
|
+
error, created_at, completed_at,
|
|
201
|
+
runtime_key, assistant_message_id, abort_reason,
|
|
177
202
|
queued_input_json, queued_ui_message_json, user_message_id,
|
|
178
203
|
regenerate_message_id,
|
|
179
204
|
run_id,
|
|
@@ -279,7 +304,7 @@ export class SubmissionRepository {
|
|
|
279
304
|
this.sql`
|
|
280
305
|
INSERT INTO pi_submissions (
|
|
281
306
|
submission_id, request_id, idempotency_key, status, accepted,
|
|
282
|
-
created_at,
|
|
307
|
+
created_at, runtime_key,
|
|
283
308
|
assistant_message_id, queued_input_json, queued_ui_message_json,
|
|
284
309
|
user_message_id, regenerate_message_id,
|
|
285
310
|
run_id, origin, scheduled_max_execution_level, unattended_execution_grant_json
|
|
@@ -290,8 +315,6 @@ export class SubmissionRepository {
|
|
|
290
315
|
'pending',
|
|
291
316
|
1,
|
|
292
317
|
${s.createdAt},
|
|
293
|
-
${s.assemblyRevision},
|
|
294
|
-
${s.assemblyDescriptor},
|
|
295
318
|
${s.runtimeKey ?? ""},
|
|
296
319
|
${s.assistantMessageId},
|
|
297
320
|
${s.queuedInputJson ?? null},
|
|
@@ -336,31 +359,17 @@ export class SubmissionRepository {
|
|
|
336
359
|
this.sql`
|
|
337
360
|
INSERT INTO pi_submissions (
|
|
338
361
|
submission_id, request_id, idempotency_key, status, accepted,
|
|
339
|
-
error, created_at, completed_at,
|
|
340
|
-
|
|
362
|
+
error, created_at, completed_at,
|
|
363
|
+
runtime_key, assistant_message_id, abort_reason,
|
|
341
364
|
run_id, admission_retryable, user_message_id
|
|
342
365
|
) VALUES (
|
|
343
366
|
${s.submissionId}, ${s.requestId}, ${s.idempotencyKey}, 'skipped', 0,
|
|
344
|
-
${s.code}, ${s.createdAt}, ${s.createdAt}, '',
|
|
367
|
+
${s.code}, ${s.createdAt}, ${s.createdAt}, '', ${s.assistantMessageId ?? s.submissionId}, NULL,
|
|
345
368
|
${s.submissionId}, ${s.retryable ? 1 : 0}, ${s.userMessageId ?? null}
|
|
346
369
|
)
|
|
347
370
|
`;
|
|
348
371
|
}
|
|
349
372
|
|
|
350
|
-
pinAssembly(id: string, revision: string, descriptor: string): boolean {
|
|
351
|
-
const updated = this.sql<{ submission_id: string }>`
|
|
352
|
-
UPDATE pi_submissions
|
|
353
|
-
SET assembly_revision = ${revision},
|
|
354
|
-
assembly_descriptor = ${descriptor}
|
|
355
|
-
WHERE submission_id = ${id}
|
|
356
|
-
AND assembly_revision = ''
|
|
357
|
-
AND assembly_descriptor = ''
|
|
358
|
-
AND status IN ('pending', 'running')
|
|
359
|
-
RETURNING submission_id
|
|
360
|
-
`;
|
|
361
|
-
return updated.length === 1;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
373
|
recordUnattendedFailure(id: string, reason: string): void {
|
|
365
374
|
this.sql`
|
|
366
375
|
UPDATE pi_submissions
|
|
@@ -53,6 +53,7 @@ export class RuntimeTelemetryCoordinator {
|
|
|
53
53
|
private delivery?: TelemetryDelivery;
|
|
54
54
|
private configured = false;
|
|
55
55
|
private draining?: Promise<void>;
|
|
56
|
+
private drainRequested = false;
|
|
56
57
|
private readonly coldBuffer: BufferedTelemetryFact[] = [];
|
|
57
58
|
|
|
58
59
|
constructor(
|
|
@@ -182,11 +183,19 @@ export class RuntimeTelemetryCoordinator {
|
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
private queueDrain(): void {
|
|
185
|
-
if (!this.delivery
|
|
186
|
+
if (!this.delivery) return;
|
|
187
|
+
// A drain reads its batch before publishing; facts written after that wait for the next one.
|
|
188
|
+
if (this.draining) {
|
|
189
|
+
this.drainRequested = true;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.drainRequested = false;
|
|
186
193
|
const pending = this.drain();
|
|
187
194
|
let tracked: Promise<void>;
|
|
188
195
|
tracked = pending.finally(() => {
|
|
189
|
-
if (this.draining
|
|
196
|
+
if (this.draining !== tracked) return;
|
|
197
|
+
this.draining = undefined;
|
|
198
|
+
if (this.drainRequested) this.queueDrain();
|
|
190
199
|
});
|
|
191
200
|
this.draining = tracked;
|
|
192
201
|
this.options.waitUntil(tracked);
|
|
@@ -196,7 +205,8 @@ export class RuntimeTelemetryCoordinator {
|
|
|
196
205
|
if (!this.delivery) return;
|
|
197
206
|
try {
|
|
198
207
|
const result = await this.delivery.drain();
|
|
199
|
-
if
|
|
208
|
+
// A queued follow-up drain schedules its own retry if it still leaves work.
|
|
209
|
+
if (!result.hasPending || this.drainRequested) return;
|
|
200
210
|
await this.options.scheduleRetry(
|
|
201
211
|
TELEMETRY_RETRY_DELAY_MS / 1_000,
|
|
202
212
|
idempotentRetry,
|
|
@@ -71,7 +71,6 @@ export class RuntimeTelemetry {
|
|
|
71
71
|
|
|
72
72
|
turnStarted(input: Readonly<{
|
|
73
73
|
submissionId: string;
|
|
74
|
-
assemblyRevision?: string;
|
|
75
74
|
input?: unknown;
|
|
76
75
|
occurredAt?: number;
|
|
77
76
|
}>): TelemetryRecordResult {
|
|
@@ -80,9 +79,6 @@ export class RuntimeTelemetry {
|
|
|
80
79
|
input.submissionId,
|
|
81
80
|
);
|
|
82
81
|
return this.record("turn.started", operationId, {
|
|
83
|
-
...(input.assemblyRevision
|
|
84
|
-
? { assemblyRevision: input.assemblyRevision }
|
|
85
|
-
: {}),
|
|
86
82
|
...(input.input === undefined ? {} : { input: input.input as never }),
|
|
87
83
|
}, input);
|
|
88
84
|
}
|
|
@@ -3,6 +3,8 @@ import { modelAttachmentText } from "../../conversation/message/conversion";
|
|
|
3
3
|
import type { PiToolCandidate } from "../runtime/contracts";
|
|
4
4
|
import { candidate } from "./candidate";
|
|
5
5
|
|
|
6
|
+
export const ASK_USER_TOOL_NAME = "ask_user";
|
|
7
|
+
|
|
6
8
|
const askUserResponseTypes = [
|
|
7
9
|
"text",
|
|
8
10
|
"single_select",
|
|
@@ -240,7 +242,7 @@ function isAskUserResponse(input: unknown, value: unknown): boolean {
|
|
|
240
242
|
export function askUserPiToolCandidate(): PiToolCandidate {
|
|
241
243
|
return {
|
|
242
244
|
...candidate({
|
|
243
|
-
name:
|
|
245
|
+
name: ASK_USER_TOOL_NAME,
|
|
244
246
|
label: "Ask user",
|
|
245
247
|
description:
|
|
246
248
|
"Ask all 1-10 user questions needed to continue in one call. Required input for every question: question and responseType; required defaults to false, so set it to true only when progress cannot continue without an answer. Use responseType text for free text, single_select or multi_select with 2-6 distinct options for choices, and attachment for files. At most one attachment question is allowed and it accepts multiple files. Do not add file-specific fields such as accept or tell the user to use the normal chat composer. The answers return as this tool's result, so continue the same turn after calling it.",
|
|
@@ -12,7 +12,7 @@ const noParameters = Type.Object({}, { additionalProperties: false });
|
|
|
12
12
|
*
|
|
13
13
|
* Runtime 组装层在 Extension 装载完成后调用,模型需要发现当前可用扩展时使用该工具。
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* 候选项捕获当前配置准备阶段的只读列表,执行时不重新扫描 Extension 集合。
|
|
16
16
|
*/
|
|
17
17
|
export function listExtensionsPiToolCandidate(
|
|
18
18
|
extensions: readonly PiLoadedExtension[],
|
|
@@ -22,7 +22,7 @@ export function listExtensionsPiToolCandidate(
|
|
|
22
22
|
label: "List extensions",
|
|
23
23
|
description: "List all currently loaded extensions and their tools.",
|
|
24
24
|
parameters: noParameters,
|
|
25
|
-
//
|
|
25
|
+
// 返回当前 Runtime 已加载的 Extension 快照。
|
|
26
26
|
// Pi 工具循环在模型选择 `list_extensions` 时调用,调用前允许 Turn 取消。
|
|
27
27
|
// 同时返回 count 和原列表,使模型既能快速判空又不丢失 Extension 细节。
|
|
28
28
|
async execute(_toolCallId, _input, signal) {
|
|
@@ -1115,7 +1115,7 @@ export function applyCitations(text: string, groundingMetadata: any): { text: st
|
|
|
1115
1115
|
const PROVIDER_ERROR_MAX_LENGTH = 2_048;
|
|
1116
1116
|
const NATIVE_TIMEOUT_MS = 60_000;
|
|
1117
1117
|
const OPENROUTER_MAX_OUTPUT_TOKENS = 4_096;
|
|
1118
|
-
export const WEB_SEARCH_MODEL = "deepseek/deepseek-v4-flash";
|
|
1118
|
+
export const WEB_SEARCH_MODEL = "deepseek/deepseek-v4.1-flash";
|
|
1119
1119
|
|
|
1120
1120
|
export interface WebSearchOptions {
|
|
1121
1121
|
endpoint: RuntimeModelEndpoint;
|