@akagilnc/pi-workflow-roles 0.1.2521 → 0.1.2653-next.71811ca
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 +129 -39
- package/README.zh-CN.md +128 -39
- package/dist/activation-ledger-topology.js +1 -1
- package/dist/archivist-record-entry.js +47 -26
- package/dist/auditor-dossier-tool.js +3 -1
- package/dist/compliance-transport.js +17 -26
- package/dist/doctor-contracts.js +1 -2
- package/dist/dossier-resolution.js +4 -8
- package/dist/engine-detour-tool.js +33 -8
- package/dist/engine-detour.js +1 -0
- package/dist/evidence-child-executor.js +277 -424
- package/dist/host-contracts.js +22 -0
- package/dist/institutional-resolution.js +88 -0
- package/dist/merger-contracts.js +1 -2
- package/dist/navigator-attendance.js +16 -0
- package/dist/notary-contracts.js +2 -3
- package/dist/package-contracts/fixer-output.js +1 -2
- package/dist/packaged-role-registry.js +102 -9
- package/dist/pi/in-process-session.js +671 -0
- package/dist/public-cli/config.js +7 -5
- package/dist/public-cli/main.js +3767 -4113
- package/dist/reviewer-child-executor.js +6 -1
- package/dist/session-opening-materials.js +10 -38
- package/dist/sitian-appender.js +146 -0
- package/dist/sitian-contracts.js +21 -0
- package/dist/sitian-facade.js +15 -0
- package/dist/sitian-reader.js +52 -0
- package/extensions/role-runtime.ts +9 -7
- package/package.json +5 -5
- package/resources/engines/cursor.md +12 -26
- package/resources/engines/hermes.md +2 -23
- package/resources/engines/opus.md +1 -16
- package/resources/navigator-route-playbook.md +2 -2
- package/scripts/build-package.mjs +0 -1
- package/souls/quality-law.md +1 -1
- package/src/activation-ledger-topology.ts +1 -1
- package/src/archivist-record-entry.ts +57 -45
- package/src/auditor-dossier-tool.ts +4 -2
- package/src/canonical-skill-binding.ts +37 -27
- package/src/collector-role.ts +11 -20
- package/src/collector-tool-schemas.ts +1 -4
- package/src/compliance-transport.ts +19 -44
- package/src/doctor-auditor.ts +1 -5
- package/src/doctor-contracts.ts +1 -4
- package/src/doctor-role.ts +31 -8
- package/src/dossier-resolution.ts +8 -8
- package/src/engine-detour-tool.ts +48 -19
- package/src/engine-detour.ts +3 -0
- package/src/evidence-child-executor.ts +277 -485
- package/src/factory-board.ts +3 -3
- package/src/gatekeeper-role.ts +21 -10
- package/src/host-contracts.ts +386 -0
- package/src/in-process-session.ts +1 -115
- package/src/institutional-resolution.ts +135 -0
- package/src/judge-auditor.ts +1 -5
- package/src/judge-role.ts +37 -51
- package/src/merger-contracts.ts +1 -4
- package/src/merger-role.ts +7 -11
- package/src/navigator-attendance.ts +20 -1
- package/src/notary-contracts.ts +8 -11
- package/src/notary-role.ts +7 -23
- package/src/notary-source-run.ts +4 -3
- package/src/package-contracts/fixer-output.ts +1 -4
- package/src/package-resources/method-skill-binding.ts +10 -34
- package/src/packaged-role-registry.ts +122 -12
- package/src/pi/adapter.ts +192 -0
- package/src/pi/durable-principal.ts +98 -0
- package/src/pi/in-process-session.ts +819 -0
- package/src/pi/known-failure.ts +77 -0
- package/src/pi/pi-normalization.ts +284 -0
- package/src/pi/role-turn-host.ts +459 -0
- package/src/public-cli/auto-resume.ts +42 -44
- package/src/public-cli/cli.ts +171 -257
- package/src/public-cli/coder-run.ts +115 -433
- package/src/public-cli/collector-run.ts +62 -314
- package/src/public-cli/config.ts +7 -5
- package/src/public-cli/doctor-run.ts +41 -52
- package/src/public-cli/fixer-run.ts +122 -427
- package/src/public-cli/invocation.ts +287 -96
- package/src/public-cli/judge-run.ts +92 -422
- package/src/public-cli/merger-run.ts +167 -429
- package/src/public-cli/notary-run.ts +41 -47
- package/src/public-cli/option-definitions.ts +136 -3
- package/src/public-cli/post-admission.ts +497 -0
- package/src/public-cli/public-run-credentials.ts +4 -4
- package/src/public-cli/reviewer-dispatch-rejection.ts +106 -0
- package/src/public-cli/reviewer-run.ts +132 -449
- package/src/public-cli/run-lifecycle.ts +339 -327
- package/src/public-cli/settlement.ts +251 -134
- package/src/public-cli/terminal.ts +3 -9
- package/src/public-cli/turn-request.ts +87 -0
- package/src/reviewer-child-executor.ts +9 -1
- package/src/reviewer-role.ts +11 -20
- package/src/role-runtime.ts +129 -78
- package/src/session-opening-materials.ts +25 -39
- package/src/sitian-appender.ts +171 -0
- package/src/sitian-contracts.ts +106 -0
- package/src/sitian-facade.ts +18 -0
- package/src/sitian-reader.ts +62 -0
- package/src/worker-role.ts +42 -40
- package/src/worker-submission-gates.ts +33 -0
- package/dist/archivist-role-run-coordinates.js +0 -28
- package/dist/package-contracts/terminating-infrastructure.js +0 -90
- package/resources/engines/sonnet.md +0 -27
- package/resources/engines/zcode.md +0 -63
- package/src/archivist-role-run-coordinates.ts +0 -45
- package/src/package-contracts/terminating-infrastructure.ts +0 -132
- package/src/public-cli/explicit-internal.ts +0 -427
- package/src/public-cli/one-shot-dispatch.ts +0 -300
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi Institutional Sub-Session Open Seam (#518 / #233).
|
|
3
|
+
* Adapts Pi AgentSession / ModelRuntime / ModelRegistry into the host-neutral
|
|
4
|
+
* HostInstitutionalSessionHandle contract.
|
|
5
|
+
* Zero Pi type leakage out of this module.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { createAgentSession, DefaultResourceLoader, ModelRegistry, ModelRuntime, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { createAssistantMessageEventStream, InMemoryCredentialStore, } from "@earendil-works/pi-ai";
|
|
12
|
+
import { createRecordSession } from "../archivist-record-entry.js";
|
|
13
|
+
import { createStreamIdleGuard, isStreamIdleTimeoutError, StreamIdleTimeoutError, } from "../stream-idle-guard.js";
|
|
14
|
+
import { hasUpstreamErrorTestimony, isNonSuccessHttpStatus, projectConfirmedRemotePayload, } from "../upstream-error-testimony.js";
|
|
15
|
+
export const DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES = 2;
|
|
16
|
+
/** Recover a typed idle timeout from a thrown error or an error-stop message. */
|
|
17
|
+
function streamIdleTimeoutFromUnknown(value) {
|
|
18
|
+
if (isStreamIdleTimeoutError(value))
|
|
19
|
+
return value;
|
|
20
|
+
const message = value instanceof Error ? value.message : typeof value === "string" ? value : undefined;
|
|
21
|
+
if (message === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
const match = /stream idle timeout after (\d+)ms/i.exec(message);
|
|
24
|
+
return match !== null && match[1] !== undefined
|
|
25
|
+
? new StreamIdleTimeoutError(Number(match[1]))
|
|
26
|
+
: undefined;
|
|
27
|
+
}
|
|
28
|
+
function resolveSessionManager(options) {
|
|
29
|
+
if (options.sessionManager !== undefined)
|
|
30
|
+
return options.sessionManager;
|
|
31
|
+
return createRecordSession({
|
|
32
|
+
cwd: options.cwd,
|
|
33
|
+
kind: options.kind,
|
|
34
|
+
...(options.subject === undefined ? {} : { subject: options.subject }),
|
|
35
|
+
...(options.parent === undefined ? {} : { parent: options.parent }),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export async function openInProcessAgentSession(options) {
|
|
39
|
+
const settings = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false } });
|
|
40
|
+
const sessionManager = resolveSessionManager(options);
|
|
41
|
+
const createArgs = {
|
|
42
|
+
cwd: options.cwd,
|
|
43
|
+
model: options.model,
|
|
44
|
+
thinkingLevel: options.thinkingLevel ?? "off",
|
|
45
|
+
modelRuntime: options.modelRuntime,
|
|
46
|
+
sessionManager,
|
|
47
|
+
settingsManager: settings,
|
|
48
|
+
...(options.noTools === undefined ? {} : { noTools: options.noTools }),
|
|
49
|
+
...(options.tools === undefined ? {} : { tools: options.tools }),
|
|
50
|
+
...(options.customTools === undefined ? {} : { customTools: options.customTools }),
|
|
51
|
+
};
|
|
52
|
+
if (options.agentDir !== undefined) {
|
|
53
|
+
const loader = new DefaultResourceLoader({
|
|
54
|
+
cwd: options.cwd,
|
|
55
|
+
agentDir: options.agentDir,
|
|
56
|
+
settingsManager: settings,
|
|
57
|
+
noExtensions: true,
|
|
58
|
+
noSkills: true,
|
|
59
|
+
noPromptTemplates: true,
|
|
60
|
+
noThemes: true,
|
|
61
|
+
noContextFiles: true,
|
|
62
|
+
...(options.systemPrompt === undefined ? {} : { systemPrompt: options.systemPrompt }),
|
|
63
|
+
});
|
|
64
|
+
await loader.reload();
|
|
65
|
+
createArgs.agentDir = options.agentDir;
|
|
66
|
+
createArgs.resourceLoader = loader;
|
|
67
|
+
}
|
|
68
|
+
else if (options.systemPrompt !== undefined) {
|
|
69
|
+
throw new Error("openInProcessAgentSession requires agentDir when systemPrompt is set");
|
|
70
|
+
}
|
|
71
|
+
const { session } = await createAgentSession(createArgs);
|
|
72
|
+
return {
|
|
73
|
+
session,
|
|
74
|
+
dispose() {
|
|
75
|
+
session.dispose();
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// ── Stream / remote error projection helpers ───────────────────────────────
|
|
80
|
+
function emptyUsage() {
|
|
81
|
+
return {
|
|
82
|
+
input: 0,
|
|
83
|
+
output: 0,
|
|
84
|
+
cacheRead: 0,
|
|
85
|
+
cacheWrite: 0,
|
|
86
|
+
totalTokens: 0,
|
|
87
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function addUsage(total, next) {
|
|
91
|
+
if (!next)
|
|
92
|
+
return;
|
|
93
|
+
total.input += next.input;
|
|
94
|
+
total.output += next.output;
|
|
95
|
+
total.cacheRead += next.cacheRead;
|
|
96
|
+
total.cacheWrite += next.cacheWrite;
|
|
97
|
+
total.totalTokens += next.totalTokens;
|
|
98
|
+
total.cost.input += next.cost?.input ?? 0;
|
|
99
|
+
total.cost.output += next.cost?.output ?? 0;
|
|
100
|
+
total.cost.cacheRead += next.cost?.cacheRead ?? 0;
|
|
101
|
+
total.cost.cacheWrite += next.cost?.cacheWrite ?? 0;
|
|
102
|
+
total.cost.total += next.cost?.total ?? 0;
|
|
103
|
+
}
|
|
104
|
+
function numericHttpStatus(value) {
|
|
105
|
+
return isNonSuccessHttpStatus(value) ? value : undefined;
|
|
106
|
+
}
|
|
107
|
+
function projectStructuredRemote(error) {
|
|
108
|
+
let httpStatus;
|
|
109
|
+
let diagnostics;
|
|
110
|
+
let body;
|
|
111
|
+
let code;
|
|
112
|
+
let errno;
|
|
113
|
+
let cursor = error;
|
|
114
|
+
const seen = new Set();
|
|
115
|
+
while (typeof cursor === "object" && cursor !== null && !seen.has(cursor)) {
|
|
116
|
+
seen.add(cursor);
|
|
117
|
+
const record = cursor;
|
|
118
|
+
const nodeStatus = numericHttpStatus(record.statusCode)
|
|
119
|
+
?? numericHttpStatus(record.status)
|
|
120
|
+
?? numericHttpStatus(record.httpStatus);
|
|
121
|
+
const nodeDiagnostics = Array.isArray(record.diagnostics) && record.diagnostics.length > 0
|
|
122
|
+
? record.diagnostics
|
|
123
|
+
: undefined;
|
|
124
|
+
const nodeHasTestimony = hasUpstreamErrorTestimony({
|
|
125
|
+
...(nodeStatus === undefined ? {} : { httpStatus: nodeStatus }),
|
|
126
|
+
...(nodeDiagnostics === undefined ? {} : { diagnostics: nodeDiagnostics }),
|
|
127
|
+
});
|
|
128
|
+
if (httpStatus === undefined && nodeStatus !== undefined)
|
|
129
|
+
httpStatus = nodeStatus;
|
|
130
|
+
if (diagnostics === undefined && nodeDiagnostics !== undefined)
|
|
131
|
+
diagnostics = nodeDiagnostics;
|
|
132
|
+
if (nodeHasTestimony) {
|
|
133
|
+
const payload = projectConfirmedRemotePayload(record);
|
|
134
|
+
if (body === undefined && payload.body !== undefined)
|
|
135
|
+
body = payload.body;
|
|
136
|
+
if (code === undefined && payload.code !== undefined)
|
|
137
|
+
code = payload.code;
|
|
138
|
+
if (errno === undefined && payload.errno !== undefined)
|
|
139
|
+
errno = payload.errno;
|
|
140
|
+
}
|
|
141
|
+
cursor = record.cause;
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
hasTestimony: hasUpstreamErrorTestimony({
|
|
145
|
+
...(httpStatus === undefined ? {} : { httpStatus }),
|
|
146
|
+
...(diagnostics === undefined ? {} : { diagnostics }),
|
|
147
|
+
}),
|
|
148
|
+
...(httpStatus === undefined ? {} : { httpStatus }),
|
|
149
|
+
...(diagnostics === undefined ? {} : { diagnostics }),
|
|
150
|
+
...(body === undefined ? {} : { body }),
|
|
151
|
+
...(code === undefined ? {} : { code }),
|
|
152
|
+
...(errno === undefined ? {} : { errno }),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function attachObservedHttpStatus(message, observedHttpStatus) {
|
|
156
|
+
if (observedHttpStatus === undefined)
|
|
157
|
+
return message;
|
|
158
|
+
if (message.stopReason !== "error" && message.stopReason !== "aborted")
|
|
159
|
+
return message;
|
|
160
|
+
if (numericHttpStatus(observedHttpStatus) === undefined)
|
|
161
|
+
return message;
|
|
162
|
+
if (projectStructuredRemote(message).httpStatus !== undefined)
|
|
163
|
+
return message;
|
|
164
|
+
return Object.assign(message, {
|
|
165
|
+
status: observedHttpStatus,
|
|
166
|
+
statusCode: observedHttpStatus,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function enrichStreamEvent(event, observedHttpStatus) {
|
|
170
|
+
if (observedHttpStatus === undefined || event === null || typeof event !== "object")
|
|
171
|
+
return event;
|
|
172
|
+
const record = event;
|
|
173
|
+
if (record.type === "error" && record.error !== null && typeof record.error === "object") {
|
|
174
|
+
return {
|
|
175
|
+
...record,
|
|
176
|
+
error: attachObservedHttpStatus(record.error, observedHttpStatus),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (record.type === "done" && record.message !== null && typeof record.message === "object") {
|
|
180
|
+
return {
|
|
181
|
+
...record,
|
|
182
|
+
message: attachObservedHttpStatus(record.message, observedHttpStatus),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (record.partial !== null && typeof record.partial === "object") {
|
|
186
|
+
return {
|
|
187
|
+
...record,
|
|
188
|
+
partial: attachObservedHttpStatus(record.partial, observedHttpStatus),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return event;
|
|
192
|
+
}
|
|
193
|
+
export async function openPiInstitutionalSession(options) {
|
|
194
|
+
const label = options.label ?? "Institutional sub-session";
|
|
195
|
+
const selection = options.selection;
|
|
196
|
+
// 1. Scratch management
|
|
197
|
+
let scratchDir;
|
|
198
|
+
let resolvedAgentDir = options.agentDir;
|
|
199
|
+
if (resolvedAgentDir === undefined) {
|
|
200
|
+
scratchDir = await mkdtemp(join(options.credentialScratchParent ?? tmpdir(), "ak-institutional-"));
|
|
201
|
+
resolvedAgentDir = scratchDir;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
// 2. Auth resolution in Pi layer via explicit selection (Hop 3)
|
|
205
|
+
// spec-2: adapter creates its OWN child-local ModelRuntime and ModelRegistry.
|
|
206
|
+
// Auth is resolved strictly by explicit selection on the child registry — never
|
|
207
|
+
// ambiently inherited from the parent ExtensionContext/modelRegistry.
|
|
208
|
+
const childRuntime = await ModelRuntime.create();
|
|
209
|
+
const childRegistry = new ModelRegistry(childRuntime);
|
|
210
|
+
const childProvider = typeof childRegistry.getProvider === "function"
|
|
211
|
+
? childRegistry.getProvider(selection.provider)
|
|
212
|
+
: undefined;
|
|
213
|
+
const foundModel = typeof childRegistry.find === "function"
|
|
214
|
+
? childRegistry.find(selection.provider, selection.model)
|
|
215
|
+
: undefined;
|
|
216
|
+
const providerDefaultModel = childProvider?.getModels?.()[0];
|
|
217
|
+
const fallbackApi = providerDefaultModel?.api
|
|
218
|
+
?? childProvider?.api
|
|
219
|
+
?? (selection.provider === "openai-codex" ? "openai-codex-responses" : "openai-completions");
|
|
220
|
+
const modelToUse = foundModel ?? {
|
|
221
|
+
id: selection.model,
|
|
222
|
+
name: selection.model,
|
|
223
|
+
api: fallbackApi,
|
|
224
|
+
provider: selection.provider,
|
|
225
|
+
baseUrl: providerDefaultModel?.baseUrl ?? "",
|
|
226
|
+
reasoning: selection.thinking !== undefined && selection.thinking !== "off",
|
|
227
|
+
input: ["text"],
|
|
228
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
229
|
+
contextWindow: 128000,
|
|
230
|
+
maxTokens: 16384,
|
|
231
|
+
};
|
|
232
|
+
let resolution;
|
|
233
|
+
if (typeof childRegistry.getProviderAuth === "function") {
|
|
234
|
+
resolution = await childRegistry.getProviderAuth(selection.provider).catch((error) => {
|
|
235
|
+
throw new Error(`${label} authentication failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
236
|
+
});
|
|
237
|
+
if (resolution === undefined) {
|
|
238
|
+
throw new Error(`${label} authentication failed: provider is not configured: ${selection.provider}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
let authResult;
|
|
242
|
+
if (typeof childRegistry.getApiKeyAndHeaders === "function") {
|
|
243
|
+
authResult = await childRegistry.getApiKeyAndHeaders(modelToUse);
|
|
244
|
+
if (authResult && !authResult.ok) {
|
|
245
|
+
throw new Error(`${label} authentication failed: ${authResult.error}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const resolvedApiKey = authResult?.apiKey ?? resolution?.auth?.apiKey;
|
|
249
|
+
const resolvedHeaders = authResult?.headers ?? resolution?.auth?.headers;
|
|
250
|
+
const resolvedEnv = authResult?.env ?? resolution?.env;
|
|
251
|
+
const effectiveBaseUrl = resolution?.auth?.baseUrl ?? modelToUse.baseUrl;
|
|
252
|
+
const effectiveModel = {
|
|
253
|
+
...modelToUse,
|
|
254
|
+
baseUrl: effectiveBaseUrl,
|
|
255
|
+
};
|
|
256
|
+
// 3. Standalone ModelRuntime + Provider with idle-only retry.
|
|
257
|
+
// spec-2: the adapter constructs its OWN runtime/provider registration.
|
|
258
|
+
// Auth is resolved only by explicit selection above and seeded into the
|
|
259
|
+
// child runtime's own credential store — never inherited ambiently from
|
|
260
|
+
// the parent ExtensionContext/Provider.
|
|
261
|
+
const credentials = new InMemoryCredentialStore();
|
|
262
|
+
const runtime = await ModelRuntime.create({
|
|
263
|
+
credentials,
|
|
264
|
+
modelsPath: null,
|
|
265
|
+
});
|
|
266
|
+
// Seed the child runtime's own credential store with the explicitly
|
|
267
|
+
// resolved auth so stream auth resolution is self-contained. No ambient
|
|
268
|
+
// fallback: absence of a resolved apiKey is not silently supplied.
|
|
269
|
+
if (resolvedApiKey !== undefined) {
|
|
270
|
+
await credentials.modify(selection.provider, async () => ({
|
|
271
|
+
type: "api_key",
|
|
272
|
+
key: resolvedApiKey,
|
|
273
|
+
...(resolvedEnv === undefined ? {} : { env: resolvedEnv }),
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
const abortReason = (signal) => signal.reason ?? new Error(`${label} provider stream aborted`);
|
|
277
|
+
let streamFailureValue;
|
|
278
|
+
async function waitForStream(promise, signal) {
|
|
279
|
+
if (signal.aborted)
|
|
280
|
+
throw abortReason(signal);
|
|
281
|
+
let onAbort;
|
|
282
|
+
try {
|
|
283
|
+
return await Promise.race([
|
|
284
|
+
promise,
|
|
285
|
+
new Promise((_resolve, reject) => {
|
|
286
|
+
onAbort = () => reject(abortReason(signal));
|
|
287
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
288
|
+
}),
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
if (onAbort !== undefined)
|
|
293
|
+
signal.removeEventListener("abort", onAbort);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const createRetriedStream = (simple, model, context, request) => {
|
|
297
|
+
const wrapped = createAssistantMessageEventStream();
|
|
298
|
+
void (async () => {
|
|
299
|
+
for (let attempt = 0;; attempt += 1) {
|
|
300
|
+
const idle = createStreamIdleGuard(options.signal === undefined ? {} : { parentSignal: options.signal });
|
|
301
|
+
let observedHttpStatus;
|
|
302
|
+
try {
|
|
303
|
+
const requestSignal = request?.signal;
|
|
304
|
+
const streamSignal = requestSignal === undefined
|
|
305
|
+
? idle.signal
|
|
306
|
+
: AbortSignal.any([idle.signal, requestSignal]);
|
|
307
|
+
const priorOnResponse = request?.onResponse;
|
|
308
|
+
const retriedRequest = {
|
|
309
|
+
...(request ?? {}),
|
|
310
|
+
...(resolvedEnv === undefined ? {} : { env: resolvedEnv }),
|
|
311
|
+
signal: streamSignal,
|
|
312
|
+
maxRetries: 0,
|
|
313
|
+
onResponse: async (response, resModel) => {
|
|
314
|
+
if (typeof response?.status === "number")
|
|
315
|
+
observedHttpStatus = response.status;
|
|
316
|
+
await priorOnResponse?.(response, resModel);
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
if (childProvider === undefined) {
|
|
320
|
+
throw new Error(`${label} provider not found: ${model.provider}`);
|
|
321
|
+
}
|
|
322
|
+
const source = simple
|
|
323
|
+
? childProvider.streamSimple(model, context, retriedRequest)
|
|
324
|
+
: childProvider.stream(model, context, retriedRequest);
|
|
325
|
+
let sawEvent = false;
|
|
326
|
+
const attemptEvents = [];
|
|
327
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
328
|
+
while (true) {
|
|
329
|
+
const next = await waitForStream(iterator.next(), idle.signal);
|
|
330
|
+
if (next.done)
|
|
331
|
+
break;
|
|
332
|
+
sawEvent = true;
|
|
333
|
+
idle.poke();
|
|
334
|
+
attemptEvents.push(enrichStreamEvent(next.value, observedHttpStatus));
|
|
335
|
+
}
|
|
336
|
+
const response = attachObservedHttpStatus(await waitForStream(source.result(), idle.signal), observedHttpStatus);
|
|
337
|
+
// Some stream APIs (e.g. OpenAI-completions over an HTTP error)
|
|
338
|
+
// surface the failure as an error-stop message rather than throwing.
|
|
339
|
+
// Hold that primary provider failure at the adapter boundary so
|
|
340
|
+
// consumers surface a real transport failure, never a projected
|
|
341
|
+
// step-machine "error" response (ADR 0018 / #518 §3).
|
|
342
|
+
if (response.stopReason === "error") {
|
|
343
|
+
const errorMessage = response.errorMessage ?? `${label} provider stream failed`;
|
|
344
|
+
const idleFailure = streamIdleTimeoutFromUnknown(errorMessage);
|
|
345
|
+
if (options.idleRetry !== false
|
|
346
|
+
&& idleFailure !== undefined
|
|
347
|
+
&& attempt < DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES
|
|
348
|
+
&& options.signal?.aborted !== true) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
streamFailureValue = idleFailure ?? new Error(errorMessage, { cause: response });
|
|
352
|
+
}
|
|
353
|
+
for (const ev of attemptEvents) {
|
|
354
|
+
wrapped.push(ev);
|
|
355
|
+
}
|
|
356
|
+
wrapped.end(response);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
if (request?.signal?.aborted) {
|
|
361
|
+
const response = {
|
|
362
|
+
role: "assistant",
|
|
363
|
+
content: [],
|
|
364
|
+
api: model.api,
|
|
365
|
+
provider: model.provider,
|
|
366
|
+
model: model.id,
|
|
367
|
+
usage: emptyUsage(),
|
|
368
|
+
stopReason: "aborted",
|
|
369
|
+
errorMessage: `${label} session aborted`,
|
|
370
|
+
timestamp: Date.now(),
|
|
371
|
+
};
|
|
372
|
+
wrapped.push({ type: "error", reason: "aborted", error: response });
|
|
373
|
+
wrapped.end(response);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const failure = isStreamIdleTimeoutError(idle.signal.reason) ? idle.signal.reason : error;
|
|
377
|
+
const idleFailure = streamIdleTimeoutFromUnknown(failure);
|
|
378
|
+
if (options.idleRetry !== false
|
|
379
|
+
&& idleFailure !== undefined
|
|
380
|
+
&& attempt < DEFAULT_COMPLIANCE_IDLE_MAX_RETRIES
|
|
381
|
+
&& options.signal?.aborted !== true) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const typedFailure = idleFailure ?? failure;
|
|
385
|
+
// Hold the primary provider failure at the adapter boundary (ADR
|
|
386
|
+
// 0018 / 失败诚实宪法). Consumers that surface transport failures
|
|
387
|
+
// read this so the real cause is never masked by a projected
|
|
388
|
+
// step-machine "error" response.
|
|
389
|
+
streamFailureValue = typedFailure;
|
|
390
|
+
const projected = projectStructuredRemote(failure);
|
|
391
|
+
const httpStatus = projected.httpStatus ?? numericHttpStatus(observedHttpStatus);
|
|
392
|
+
const response = {
|
|
393
|
+
role: "assistant",
|
|
394
|
+
content: [],
|
|
395
|
+
api: model.api,
|
|
396
|
+
provider: model.provider,
|
|
397
|
+
model: model.id,
|
|
398
|
+
usage: emptyUsage(),
|
|
399
|
+
stopReason: "error",
|
|
400
|
+
errorMessage: failure instanceof Error ? failure.message : String(failure),
|
|
401
|
+
timestamp: Date.now(),
|
|
402
|
+
...(projected.diagnostics === undefined ? {} : { diagnostics: projected.diagnostics }),
|
|
403
|
+
...(httpStatus === undefined ? {} : { status: httpStatus, statusCode: httpStatus }),
|
|
404
|
+
...(projected.body === undefined ? {} : { body: projected.body }),
|
|
405
|
+
...(projected.code === undefined ? {} : { code: projected.code }),
|
|
406
|
+
...(projected.errno === undefined ? {} : { errno: projected.errno }),
|
|
407
|
+
};
|
|
408
|
+
wrapped.push({ type: "error", reason: "error", error: response });
|
|
409
|
+
wrapped.end(response);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
idle.dispose();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
})();
|
|
417
|
+
return wrapped;
|
|
418
|
+
};
|
|
419
|
+
const provider = {
|
|
420
|
+
id: selection.provider,
|
|
421
|
+
name: childProvider?.name ?? label,
|
|
422
|
+
...(effectiveBaseUrl ? { baseUrl: effectiveBaseUrl } : {}),
|
|
423
|
+
...(resolvedHeaders ? { headers: resolvedHeaders } : {}),
|
|
424
|
+
auth: {
|
|
425
|
+
apiKey: {
|
|
426
|
+
name: `${label} authentication`,
|
|
427
|
+
async resolve() {
|
|
428
|
+
return {
|
|
429
|
+
auth: {
|
|
430
|
+
...(resolvedApiKey === undefined ? {} : { apiKey: resolvedApiKey }),
|
|
431
|
+
...(resolvedHeaders === undefined ? {} : { headers: resolvedHeaders }),
|
|
432
|
+
...(effectiveBaseUrl === undefined ? {} : { baseUrl: effectiveBaseUrl }),
|
|
433
|
+
},
|
|
434
|
+
...(resolvedEnv === undefined ? {} : { env: resolvedEnv }),
|
|
435
|
+
};
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
getModels() { return [effectiveModel]; },
|
|
440
|
+
stream(model, childContext, request) {
|
|
441
|
+
return createRetriedStream(false, model, childContext, request);
|
|
442
|
+
},
|
|
443
|
+
streamSimple(model, childContext, request) {
|
|
444
|
+
return createRetriedStream(true, model, childContext, request);
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
runtime.registerNativeProvider(provider);
|
|
448
|
+
await runtime.refresh({ allowNetwork: false });
|
|
449
|
+
// 4. Settings and ResourceLoader (fixed adapter policies: no extensions/skills/themes/templates/context-files)
|
|
450
|
+
const settings = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false } });
|
|
451
|
+
const loader = new DefaultResourceLoader({
|
|
452
|
+
cwd: options.cwd,
|
|
453
|
+
agentDir: resolvedAgentDir,
|
|
454
|
+
settingsManager: settings,
|
|
455
|
+
noExtensions: true,
|
|
456
|
+
noSkills: true,
|
|
457
|
+
noPromptTemplates: true,
|
|
458
|
+
noThemes: true,
|
|
459
|
+
noContextFiles: true,
|
|
460
|
+
systemPrompt: options.systemPrompt,
|
|
461
|
+
});
|
|
462
|
+
await loader.reload();
|
|
463
|
+
// 5. SessionManager resolution
|
|
464
|
+
let sessionManager;
|
|
465
|
+
if (options.sessionManager !== undefined) {
|
|
466
|
+
sessionManager = options.sessionManager;
|
|
467
|
+
}
|
|
468
|
+
else if (options.sessionIdentity !== undefined) {
|
|
469
|
+
sessionManager = createRecordSession({
|
|
470
|
+
cwd: options.cwd,
|
|
471
|
+
kind: options.sessionIdentity.kind,
|
|
472
|
+
...(options.sessionIdentity.subject === undefined ? {} : { subject: options.sessionIdentity.subject }),
|
|
473
|
+
...(options.sessionIdentity.parent === undefined ? {} : { parent: options.sessionIdentity.parent }),
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
sessionManager = createRecordSession({
|
|
478
|
+
cwd: options.cwd,
|
|
479
|
+
kind: "institutional",
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
// 6. Tools mapping
|
|
483
|
+
const customTools = [];
|
|
484
|
+
if (options.customTools !== undefined) {
|
|
485
|
+
customTools.push(...options.customTools);
|
|
486
|
+
}
|
|
487
|
+
if (options.tools !== undefined) {
|
|
488
|
+
for (const hostTool of options.tools) {
|
|
489
|
+
customTools.push({
|
|
490
|
+
name: hostTool.name,
|
|
491
|
+
label: hostTool.label,
|
|
492
|
+
description: hostTool.description,
|
|
493
|
+
parameters: hostTool.parameters,
|
|
494
|
+
execute: async (toolCallId, params, signal, update, _ctx) => {
|
|
495
|
+
const res = await hostTool.execute(toolCallId, params, signal, update === undefined ? undefined : (u) => update(u), undefined);
|
|
496
|
+
return res;
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// 7. Create AgentSession
|
|
502
|
+
const { session } = await createAgentSession({
|
|
503
|
+
cwd: options.cwd,
|
|
504
|
+
model: effectiveModel,
|
|
505
|
+
thinkingLevel: (options.selection.thinking ?? "off"),
|
|
506
|
+
modelRuntime: runtime,
|
|
507
|
+
sessionManager,
|
|
508
|
+
settingsManager: settings,
|
|
509
|
+
agentDir: resolvedAgentDir,
|
|
510
|
+
resourceLoader: loader,
|
|
511
|
+
...(options.noTools === undefined ? {} : { noTools: options.noTools }),
|
|
512
|
+
...(options.toolsAllowlist === undefined ? {} : { tools: options.toolsAllowlist }),
|
|
513
|
+
...(customTools.length === 0 ? {} : { customTools }),
|
|
514
|
+
});
|
|
515
|
+
// 8. Event subscriptions
|
|
516
|
+
const listeners = new Set();
|
|
517
|
+
const accumulatedUsage = emptyUsage();
|
|
518
|
+
let lastEmittedAssistant;
|
|
519
|
+
const unsubscribeSession = session.subscribe((event) => {
|
|
520
|
+
if (event.type === "message_end") {
|
|
521
|
+
const msg = event.message;
|
|
522
|
+
if (msg.role === "assistant") {
|
|
523
|
+
lastEmittedAssistant = msg;
|
|
524
|
+
if (msg.usage) {
|
|
525
|
+
addUsage(accumulatedUsage, msg.usage);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
for (const listener of listeners) {
|
|
529
|
+
listener({
|
|
530
|
+
type: "message_end",
|
|
531
|
+
role: msg.role,
|
|
532
|
+
message: msg,
|
|
533
|
+
...(msg.usage === undefined ? {} : { usage: msg.usage }),
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
else if (event.type === "turn_end") {
|
|
538
|
+
const msg = event.message;
|
|
539
|
+
for (const listener of listeners) {
|
|
540
|
+
listener({
|
|
541
|
+
type: "turn_end",
|
|
542
|
+
...(msg.stopReason === undefined ? {} : { stopReason: msg.stopReason }),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
else if (event.type === "tool_execution_start") {
|
|
547
|
+
for (const listener of listeners) {
|
|
548
|
+
listener({
|
|
549
|
+
type: "tool_call",
|
|
550
|
+
toolCallId: event.toolCallId,
|
|
551
|
+
toolName: event.toolName,
|
|
552
|
+
args: event.args ?? event.input,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else if (event.type === "tool_execution_end") {
|
|
557
|
+
for (const listener of listeners) {
|
|
558
|
+
listener({
|
|
559
|
+
type: "tool_result",
|
|
560
|
+
toolCallId: event.toolCallId,
|
|
561
|
+
toolName: event.toolName,
|
|
562
|
+
isError: event.isError,
|
|
563
|
+
details: event.result ?? event.details,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
let closed = false;
|
|
569
|
+
const sessionFile = sessionManager.getSessionFile();
|
|
570
|
+
const sessionId = sessionManager.getHeader?.()?.id;
|
|
571
|
+
const handle = {
|
|
572
|
+
...(sessionFile === undefined ? {} : { sessionFile }),
|
|
573
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
574
|
+
async prompt(text) {
|
|
575
|
+
const abortSession = () => { void session.abort(); };
|
|
576
|
+
if (options.signal?.aborted)
|
|
577
|
+
abortSession();
|
|
578
|
+
else
|
|
579
|
+
options.signal?.addEventListener("abort", abortSession, { once: true });
|
|
580
|
+
let promptError;
|
|
581
|
+
try {
|
|
582
|
+
await session.prompt(text);
|
|
583
|
+
}
|
|
584
|
+
catch (error) {
|
|
585
|
+
promptError = error;
|
|
586
|
+
}
|
|
587
|
+
finally {
|
|
588
|
+
options.signal?.removeEventListener("abort", abortSession);
|
|
589
|
+
}
|
|
590
|
+
const lastAssistant = [...session.messages]
|
|
591
|
+
.reverse()
|
|
592
|
+
.find((message) => message.role === "assistant");
|
|
593
|
+
if (lastAssistant !== undefined && lastAssistant !== lastEmittedAssistant) {
|
|
594
|
+
lastEmittedAssistant = lastAssistant;
|
|
595
|
+
for (const listener of listeners) {
|
|
596
|
+
listener({
|
|
597
|
+
type: "message_end",
|
|
598
|
+
role: lastAssistant.role,
|
|
599
|
+
message: lastAssistant,
|
|
600
|
+
...(lastAssistant.usage === undefined ? {} : { usage: lastAssistant.usage }),
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (promptError !== undefined && lastAssistant === undefined) {
|
|
605
|
+
throw promptError;
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
text: session.getLastAssistantText() ?? "",
|
|
609
|
+
...(lastAssistant?.stopReason === undefined ? {} : { stopReason: lastAssistant.stopReason }),
|
|
610
|
+
...(lastAssistant?.errorMessage === undefined ? {} : { errorMessage: lastAssistant.errorMessage }),
|
|
611
|
+
usage: accumulatedUsage,
|
|
612
|
+
messages: session.messages,
|
|
613
|
+
};
|
|
614
|
+
},
|
|
615
|
+
subscribe(listener) {
|
|
616
|
+
listeners.add(listener);
|
|
617
|
+
return () => {
|
|
618
|
+
listeners.delete(listener);
|
|
619
|
+
};
|
|
620
|
+
},
|
|
621
|
+
abort() {
|
|
622
|
+
void session.abort();
|
|
623
|
+
},
|
|
624
|
+
async close() {
|
|
625
|
+
if (closed)
|
|
626
|
+
return;
|
|
627
|
+
closed = true;
|
|
628
|
+
listeners.clear();
|
|
629
|
+
try {
|
|
630
|
+
unsubscribeSession();
|
|
631
|
+
}
|
|
632
|
+
finally {
|
|
633
|
+
// spec-3: handle.close must always dispose the child session even when
|
|
634
|
+
// unsubscribing throws (the throwing unsubscribe is a cleanup failure the
|
|
635
|
+
// caller aggregates, never a reason to skip session teardown).
|
|
636
|
+
session.dispose();
|
|
637
|
+
}
|
|
638
|
+
if (scratchDir !== undefined) {
|
|
639
|
+
try {
|
|
640
|
+
await rm(scratchDir, { recursive: true, force: true });
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
throw error;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
};
|
|
648
|
+
return {
|
|
649
|
+
handle,
|
|
650
|
+
// Read lazily: the provider stream runs during session.prompt, so the
|
|
651
|
+
// primary failure is only known after that turn completes. A getter keeps
|
|
652
|
+
// this reflecting the latest value instead of freezing it at open time.
|
|
653
|
+
get streamFailure() {
|
|
654
|
+
return streamFailureValue;
|
|
655
|
+
},
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
catch (openError) {
|
|
659
|
+
if (scratchDir !== undefined) {
|
|
660
|
+
try {
|
|
661
|
+
await rm(scratchDir, { recursive: true, force: true });
|
|
662
|
+
}
|
|
663
|
+
catch (cleanupFailure) {
|
|
664
|
+
// ADR 0018 / #518 §1④: primary failure and cleanup failure stay
|
|
665
|
+
// separated — cleanup must not mask the original open cause.
|
|
666
|
+
throw new AggregateError([openError, cleanupFailure], `${label} open failed and its scratch cleanup also failed`, { cause: openError });
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
throw openError;
|
|
670
|
+
}
|
|
671
|
+
}
|