@astrosheep/keiyaku 2.9.4 → 2.9.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/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/harness/execution-handle.js +1 -0
- package/build/agents/harness/outcome.js +31 -5
- package/build/agents/harness/projection.js +151 -78
- package/build/agents/harness/router.js +2 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +13 -1
- package/build/agents/providers/claude-agent-sdk/registration.js +2 -1
- package/build/agents/providers/codex-app-server/adapter.js +43 -10
- package/build/agents/providers/codex-app-server/events.js +23 -0
- package/build/agents/providers/codex-app-server/registration.js +2 -1
- package/build/agents/providers/opencode-sdk/adapter.js +7 -0
- package/build/agents/providers/opencode-sdk/registration.js +2 -1
- package/build/agents/providers/pi/adapter.js +10 -0
- package/build/agents/providers/pi/events.js +4 -3
- package/build/agents/providers/pi/registration.js +3 -1
- package/build/agents/types.js +2 -0
- package/build/cli/commands/akuma/akuma/meta.js +1 -1
- package/build/cli/commands/akuma/list/meta-ls.js +1 -1
- package/build/cli/commands/akuma/view/meta.js +1 -1
- package/build/cli/commands/contract/amend/meta.js +4 -4
- package/build/cli/commands/contract/arc/handler.js +1 -1
- package/build/cli/commands/contract/arc/meta.js +4 -4
- package/build/cli/commands/contract/audit/handler.js +16 -0
- package/build/cli/commands/contract/audit/meta.js +16 -0
- package/build/cli/commands/contract/bind/meta.js +20 -11
- package/build/cli/commands/contract/catalog.js +2 -0
- package/build/cli/commands/contract/forfeit/meta.js +1 -1
- package/build/cli/commands/contract/log/meta.js +1 -1
- package/build/cli/commands/contract/petition/handler.js +1 -2
- package/build/cli/commands/contract/petition/meta.js +6 -6
- package/build/cli/commands/contract/renew/handler.js +2 -2
- package/build/cli/commands/contract/renew/meta.js +4 -4
- package/build/cli/commands/metadata.js +3 -2
- package/build/cli/commands/projection/call/meta.js +4 -1
- package/build/cli/commands/projection/kill/meta.js +1 -1
- package/build/cli/commands/projection/revive/meta.js +4 -1
- package/build/cli/commands/projection/status/meta.js +1 -1
- package/build/cli/commands/projection/tell/handler.js +1 -1
- package/build/cli/commands/projection/tell/meta.js +4 -1
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/skills/install/meta.js +1 -1
- package/build/cli/commands/skills/skills/meta.js +1 -1
- package/build/cli/commands/system/completion/meta.js +1 -1
- package/build/cli/commands/system/dump-env/meta.js +1 -1
- package/build/cli/commands/system/guide/meta.js +1 -1
- package/build/cli/commands/task/add/handler.js +2 -2
- package/build/cli/commands/task/add/meta.js +4 -1
- package/build/cli/commands/task/doctor/meta.js +1 -1
- package/build/cli/commands/task/done/meta.js +1 -1
- package/build/cli/commands/task/drop/meta.js +1 -1
- package/build/cli/commands/task/ls/handler.js +10 -4
- package/build/cli/commands/task/ls/meta.js +3 -3
- package/build/cli/commands/task/show/meta.js +1 -1
- package/build/cli/commands/task/start/meta.js +1 -1
- package/build/cli/commands/task/stop/meta.js +1 -1
- package/build/cli/commands/task/task/meta.js +1 -1
- package/build/cli/commands/task/update/handler.js +2 -3
- package/build/cli/commands/task/update/meta.js +4 -1
- package/build/cli/completion.js +9 -2
- package/build/cli/flags.js +19 -3
- package/build/cli/help.js +3 -3
- package/build/cli/index.js +59 -75
- package/build/cli/output-stream-error-policy.js +19 -5
- package/build/cli/parse-flags.js +19 -2
- package/build/cli/parse-metadata.js +1 -7
- package/build/cli/parse-selectors.js +1 -0
- package/build/cli/parse.js +16 -7
- package/build/cli/projection-address.js +6 -1
- package/build/cli/render/audit.js +121 -0
- package/build/cli/render/errors.js +18 -1
- package/build/cli/render/format.js +5 -2
- package/build/cli/render/misc.js +3 -1
- package/build/cli/render/projection-activity.js +12 -6
- package/build/cli/render/status.js +28 -18
- package/build/cli/render/success-response.js +6 -5
- package/build/cli/render/task.js +12 -43
- package/build/cli/render/terminal-failure.js +7 -2
- package/build/cli/render/wait.js +36 -8
- package/build/config/env-keys.js +2 -0
- package/build/config/env.js +3 -0
- package/build/core/amend.js +20 -94
- package/build/core/arc.js +14 -6
- package/build/core/audit/candidate.js +48 -0
- package/build/core/audit/coordinates.js +180 -0
- package/build/core/audit/evidence.js +158 -0
- package/build/core/audit/facade.js +42 -0
- package/build/core/audit/readiness.js +4 -0
- package/build/core/audit/report.js +38 -0
- package/build/core/audit.js +1 -0
- package/build/core/bind.js +1 -1
- package/build/core/call/execution.js +1 -0
- package/build/core/contract-view.js +2 -3
- package/build/core/draft.js +33 -20
- package/build/core/entry.js +3 -4
- package/build/core/markdown/lex.js +2 -1
- package/build/core/projection/generation/database.js +41 -16
- package/build/core/projection/generation/model.js +22 -2
- package/build/core/projection/generation/projection-generation-execution.js +20 -7
- package/build/core/projection/generation/projection-generation-launcher.js +4 -4
- package/build/core/projection/generation/projection-generation-runner.js +1 -0
- package/build/core/projection/generation/store.js +1 -1
- package/build/core/projection/generation/transitions.js +19 -3
- package/build/core/projection/index.js +2 -2
- package/build/core/projection/projection-execution-observer.js +35 -21
- package/build/core/projection/projection-life-observer.js +16 -2
- package/build/core/projection/projection-status.js +67 -22
- package/build/core/projection/projection-terminal-failure.js +2 -0
- package/build/core/projection/projection-wait.js +6 -9
- package/build/core/projection/projection-wake.js +30 -6
- package/build/core/renew.js +41 -22
- package/build/core/scope.js +129 -2
- package/build/core/seal.js +36 -15
- package/build/core/settlement/petition-claim-gates.js +4 -33
- package/build/core/settlement/petition-preview.js +1 -1
- package/build/core/settlement/petition.js +10 -3
- package/build/core/settlement/settlement.js +3 -10
- package/build/core/settlement/verification.js +59 -0
- package/build/core/status/board.js +48 -28
- package/build/core/stored-agent-event.js +3 -2
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +20 -12
- package/build/core/task/document.js +5 -0
- package/build/core/task/index.js +1 -1
- package/build/core/task/model.js +8 -1
- package/build/core/task/query.js +214 -0
- package/build/core/task/settlement-git.js +115 -16
- package/build/core/task/settlement-policy.js +17 -9
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-contract.js +125 -51
- package/build/core/task/task-store-repository.js +1 -0
- package/build/core/task/task.js +2 -0
- package/build/flow-error.js +2 -4
- package/build/generated/version.js +2 -2
- package/build/git/diff/parsers.js +23 -13
- package/build/git/diff/structured.js +127 -0
- package/build/index.js +7 -7
- package/build/telemetry/logger.js +4 -2
- package/package.json +1 -1
- package/skills/keiyaku/SKILL.md +2 -2
- package/skills/keiyaku-akuma/SKILL.md +17 -6
- package/skills/keiyaku-task/SKILL.md +16 -7
- package/skills/keiyaku-workflow/SKILL.md +48 -13
|
@@ -15,6 +15,7 @@ export function buildCallTerms(snapshot, prompt, cwd, options) {
|
|
|
15
15
|
prompt,
|
|
16
16
|
sessionContinuationPromptPrefix,
|
|
17
17
|
idleTimeoutMs: options.idleTimeoutMs ?? runtimeConfig.subagent.execIdleTimeoutMs,
|
|
18
|
+
executionTimeoutMs: options.executionTimeoutMs ?? runtimeConfig.subagent.execMaxDurationMs,
|
|
18
19
|
maxCaptureChars: registration.maxCaptureChars(runtimeConfig),
|
|
19
20
|
signal: options.signal,
|
|
20
21
|
...(options.initialTurn !== undefined ? { initialTurn: options.initialTurn } : {}),
|
|
@@ -3,13 +3,17 @@ import { SUBAGENT_SESSION_VERSION, } from "../session.js";
|
|
|
3
3
|
import { OUTCOME_ERROR_VERSION, SubagentFailure, } from "../types.js";
|
|
4
4
|
import { isProviderTerminalError, } from "./provider-adapter.js";
|
|
5
5
|
import { renderCapturedText } from "./runtime.js";
|
|
6
|
-
export function createOutcomeState(maxCaptureChars) {
|
|
6
|
+
export function createOutcomeState(maxCaptureChars, admittedSession) {
|
|
7
7
|
return {
|
|
8
8
|
outputBuffer: new StringTailBuffer(maxCaptureChars),
|
|
9
9
|
diagnosticsBuffer: new StringTailBuffer(maxCaptureChars),
|
|
10
10
|
warnings: [],
|
|
11
|
+
...(admittedSession ? { session: admittedSession } : {}),
|
|
11
12
|
};
|
|
12
13
|
}
|
|
14
|
+
export function recordSessionAdmission(state, session) {
|
|
15
|
+
state.session = session;
|
|
16
|
+
}
|
|
13
17
|
function mintOutcome(draft) {
|
|
14
18
|
return draft;
|
|
15
19
|
}
|
|
@@ -29,7 +33,6 @@ export function processEvent(state, event) {
|
|
|
29
33
|
return;
|
|
30
34
|
case "done":
|
|
31
35
|
state.doneEvent = event;
|
|
32
|
-
state.session = event.session;
|
|
33
36
|
if (event.finalMessage !== undefined) {
|
|
34
37
|
state.finalMessageCandidate = event.finalMessage;
|
|
35
38
|
}
|
|
@@ -64,7 +67,15 @@ export function failFromFailEvent(shared, state, failEvent) {
|
|
|
64
67
|
exitCode: failEvent.exitCode ?? null,
|
|
65
68
|
}, state.finalMessageCandidate, failEvent.diagnostics ?? diagnostics);
|
|
66
69
|
}
|
|
67
|
-
export function classifyFailure(shared, state, error, idleTimedOut) {
|
|
70
|
+
export function classifyFailure(shared, state, error, idleTimedOut, transportFailure, policyBudgetExceeded = false) {
|
|
71
|
+
if (policyBudgetExceeded) {
|
|
72
|
+
return failWithError(shared, state, {
|
|
73
|
+
code: "SUBAGENT_POLICY_BUDGET",
|
|
74
|
+
message: `${shared.terms.provider} execution policy budget expired`,
|
|
75
|
+
timeoutMs: shared.terms.executionTimeoutMs,
|
|
76
|
+
exitCode: null,
|
|
77
|
+
}, state.finalMessageCandidate);
|
|
78
|
+
}
|
|
68
79
|
if (shared.dismissed || shared.externallyAborted) {
|
|
69
80
|
return mintOutcome({
|
|
70
81
|
status: "dismissed",
|
|
@@ -75,6 +86,18 @@ export function classifyFailure(shared, state, error, idleTimedOut) {
|
|
|
75
86
|
warnings: [...state.warnings],
|
|
76
87
|
});
|
|
77
88
|
}
|
|
89
|
+
if (transportFailure) {
|
|
90
|
+
const message = transportFailure instanceof Error ? transportFailure.message : `${shared.terms.provider} session transport probe failed`;
|
|
91
|
+
const timeoutMs = isProviderTerminalError(transportFailure) && transportFailure.fact.kind === "failed"
|
|
92
|
+
? transportFailure.fact.timeoutMs
|
|
93
|
+
: shared.terms.idleTimeoutMs;
|
|
94
|
+
return failWithError(shared, state, {
|
|
95
|
+
code: "SUBAGENT_TRANSPORT_ERROR",
|
|
96
|
+
message,
|
|
97
|
+
timeoutMs,
|
|
98
|
+
exitCode: null,
|
|
99
|
+
}, state.finalMessageCandidate);
|
|
100
|
+
}
|
|
78
101
|
if (idleTimedOut) {
|
|
79
102
|
return failWithError(shared, state, {
|
|
80
103
|
code: "SUBAGENT_TIMEOUT",
|
|
@@ -153,13 +176,16 @@ export function failWithError(shared, state, error, finalMessage, diagnostics =
|
|
|
153
176
|
warnings: [...state.warnings],
|
|
154
177
|
});
|
|
155
178
|
}
|
|
156
|
-
export function completeOutcome(
|
|
179
|
+
export function completeOutcome(state, finalMessage) {
|
|
180
|
+
if (!state.session) {
|
|
181
|
+
throw new Error("Cannot complete an outcome before session admission");
|
|
182
|
+
}
|
|
157
183
|
return mintOutcome({
|
|
158
184
|
status: "completed",
|
|
159
185
|
finalMessage,
|
|
160
186
|
output: renderCapturedText(state.outputBuffer),
|
|
161
187
|
diagnostics: renderCapturedText(state.diagnosticsBuffer),
|
|
162
|
-
session,
|
|
188
|
+
session: state.session,
|
|
163
189
|
warnings: [...state.warnings],
|
|
164
190
|
});
|
|
165
191
|
}
|
|
@@ -2,9 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { FlowError } from "../../flow-error.js";
|
|
3
3
|
import { createEventChannel } from "./event-channel.js";
|
|
4
4
|
import { overflowMarkerFromDropped, prepareAgentEventForPersistence, } from "./execution-handle.js";
|
|
5
|
-
import { classifyFailure, classifySettlementFailure, completeOutcome, createOutcomeState, failFromFailEvent, failWithError, processEvent, renderOutcomeCapturedText, } from "./outcome.js";
|
|
5
|
+
import { classifyFailure, classifySettlementFailure, completeOutcome, createOutcomeState, failFromFailEvent, failWithError, processEvent, recordSessionAdmission, renderOutcomeCapturedText, } from "./outcome.js";
|
|
6
6
|
import { startLiveProjection } from "./router.js";
|
|
7
7
|
import { createAbortError } from "./runtime.js";
|
|
8
|
+
const POLICY_BUDGET_CLEANUP_GRACE_MS = 5_000;
|
|
8
9
|
export function call(terms, options) {
|
|
9
10
|
return createProjection(terms, undefined, options);
|
|
10
11
|
}
|
|
@@ -154,7 +155,21 @@ function requestControlledGracefulAbort(shared, reason) {
|
|
|
154
155
|
shared.gracefulAbort.catch(() => undefined);
|
|
155
156
|
return shared.gracefulAbort;
|
|
156
157
|
}
|
|
157
|
-
function
|
|
158
|
+
function requestPolicyBudgetCleanup(shared, reason) {
|
|
159
|
+
const graceful = requestControlledGracefulAbort(shared, reason);
|
|
160
|
+
const force = () => {
|
|
161
|
+
void shared.live?.abort("force").catch(() => undefined);
|
|
162
|
+
};
|
|
163
|
+
const cleanupTimeout = setTimeout(force, POLICY_BUDGET_CLEANUP_GRACE_MS);
|
|
164
|
+
cleanupTimeout.unref?.();
|
|
165
|
+
void graceful.then(() => clearTimeout(cleanupTimeout), () => {
|
|
166
|
+
clearTimeout(cleanupTimeout);
|
|
167
|
+
force();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, knownActiveTools, observerEnabled) {
|
|
171
|
+
if (shared.terminal)
|
|
172
|
+
return;
|
|
158
173
|
const prepared = prepareAgentEventForPersistence(event);
|
|
159
174
|
if (observerEnabled.value && shared.observer) {
|
|
160
175
|
try {
|
|
@@ -173,25 +188,63 @@ function deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, obse
|
|
|
173
188
|
}
|
|
174
189
|
}
|
|
175
190
|
shared.publicEvents.push(event);
|
|
191
|
+
if (event.type === "activity") {
|
|
192
|
+
if (event.phase === "started")
|
|
193
|
+
knownActiveTools.add(event.id);
|
|
194
|
+
if (event.phase === "completed")
|
|
195
|
+
knownActiveTools.delete(event.id);
|
|
196
|
+
}
|
|
176
197
|
refreshIdleTimeout();
|
|
177
198
|
processEvent(state, event);
|
|
178
199
|
}
|
|
179
200
|
async function settleProjection(shared) {
|
|
180
|
-
const state = createOutcomeState(shared.terms.maxCaptureChars);
|
|
201
|
+
const state = createOutcomeState(shared.terms.maxCaptureChars, shared.reviveTerms?.session);
|
|
181
202
|
let idleTimedOut = false;
|
|
203
|
+
let transportFailure;
|
|
204
|
+
let policyBudgetExceeded = false;
|
|
205
|
+
const knownActiveTools = new Set();
|
|
182
206
|
let abortRequested = false;
|
|
183
207
|
const observerEnabled = { value: shared.observer !== undefined };
|
|
184
208
|
let idleTimeout;
|
|
209
|
+
let executionTimeout;
|
|
210
|
+
let liveDriver;
|
|
211
|
+
let probeInFlight;
|
|
212
|
+
let resolvePolicyBudget;
|
|
213
|
+
const policyBudget = new Promise((resolve) => {
|
|
214
|
+
resolvePolicyBudget = resolve;
|
|
215
|
+
});
|
|
185
216
|
const refreshIdleTimeout = () => {
|
|
217
|
+
if (shared.terminal)
|
|
218
|
+
return;
|
|
186
219
|
if (idleTimeout)
|
|
187
220
|
clearTimeout(idleTimeout);
|
|
188
221
|
idleTimeout = setTimeout(() => {
|
|
222
|
+
if (knownActiveTools.size > 0) {
|
|
223
|
+
if (!liveDriver?.probe || probeInFlight)
|
|
224
|
+
return;
|
|
225
|
+
probeInFlight = liveDriver.probe()
|
|
226
|
+
.then(() => refreshIdleTimeout())
|
|
227
|
+
.catch((error) => {
|
|
228
|
+
transportFailure = error;
|
|
229
|
+
return requestControlledGracefulAbort(shared, error);
|
|
230
|
+
})
|
|
231
|
+
.finally(() => {
|
|
232
|
+
probeInFlight = undefined;
|
|
233
|
+
});
|
|
234
|
+
probeInFlight.catch(() => undefined);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
189
237
|
idleTimedOut = true;
|
|
190
238
|
requestControlledGracefulAbort(shared, createAbortError(`${shared.terms.provider} exec idle timed out`));
|
|
191
239
|
}, shared.terms.idleTimeoutMs);
|
|
192
240
|
idleTimeout.unref?.();
|
|
193
241
|
};
|
|
194
|
-
|
|
242
|
+
executionTimeout = setTimeout(() => {
|
|
243
|
+
policyBudgetExceeded = true;
|
|
244
|
+
requestPolicyBudgetCleanup(shared, createAbortError(`${shared.terms.provider} execution policy budget expired`));
|
|
245
|
+
resolvePolicyBudget();
|
|
246
|
+
}, shared.terms.executionTimeoutMs);
|
|
247
|
+
executionTimeout.unref?.();
|
|
195
248
|
const requestLiveAbort = () => {
|
|
196
249
|
if (shared.explicitlyAborted)
|
|
197
250
|
return;
|
|
@@ -201,90 +254,110 @@ async function settleProjection(shared) {
|
|
|
201
254
|
requestControlledGracefulAbort(shared, shared.lifecycleAbort.signal.reason);
|
|
202
255
|
};
|
|
203
256
|
shared.lifecycleAbort.signal.addEventListener("abort", requestLiveAbort, { once: true });
|
|
204
|
-
|
|
205
|
-
const live = await startLiveProjection(shared.terms, shared.lifecycleAbort.signal, shared.reviveTerms, refreshIdleTimeout, shared.observer);
|
|
206
|
-
shared.onLive(live);
|
|
207
|
-
resolveForceWaiters(shared, live);
|
|
208
|
-
if (shared.lifecycleAbort.signal.aborted) {
|
|
209
|
-
requestLiveAbort();
|
|
210
|
-
}
|
|
257
|
+
const providerSettlement = (async () => {
|
|
211
258
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
259
|
+
const live = await startLiveProjection(shared.terms, shared.lifecycleAbort.signal, shared.reviveTerms, refreshIdleTimeout, shared.observer, (admission) => {
|
|
260
|
+
recordSessionAdmission(state, admission.session);
|
|
261
|
+
refreshIdleTimeout();
|
|
262
|
+
});
|
|
263
|
+
liveDriver = live;
|
|
264
|
+
shared.onLive(live);
|
|
265
|
+
resolveForceWaiters(shared, live);
|
|
266
|
+
if (shared.lifecycleAbort.signal.aborted) {
|
|
267
|
+
requestLiveAbort();
|
|
214
268
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
type: "fail",
|
|
220
|
-
code: "PROJECTION_STATE_ERROR",
|
|
221
|
-
message: error.message,
|
|
222
|
-
});
|
|
223
|
-
if (event)
|
|
224
|
-
deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, observerEnabled);
|
|
269
|
+
try {
|
|
270
|
+
for await (const event of live.events) {
|
|
271
|
+
deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, knownActiveTools, observerEnabled);
|
|
272
|
+
}
|
|
225
273
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
274
|
+
catch (error) {
|
|
275
|
+
if (error instanceof Error && error.code === "PROJECTION_STATE_ERROR") {
|
|
276
|
+
const event = live.mintEvent({
|
|
277
|
+
type: "fail",
|
|
278
|
+
code: "PROJECTION_STATE_ERROR",
|
|
279
|
+
message: error.message,
|
|
280
|
+
});
|
|
281
|
+
if (event)
|
|
282
|
+
deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, knownActiveTools, observerEnabled);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const event = live.mintEvent({
|
|
286
|
+
type: "diagnostic",
|
|
287
|
+
text: `pump error: ${String(error)}`,
|
|
288
|
+
});
|
|
289
|
+
if (event)
|
|
290
|
+
deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, knownActiveTools, observerEnabled);
|
|
291
|
+
}
|
|
233
292
|
}
|
|
293
|
+
const completionResult = await live.completion.then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
|
|
294
|
+
const completionOutcome = completionResult.ok ? completionResult.value : undefined;
|
|
295
|
+
// Outcome classification is the terminal publication boundary. A
|
|
296
|
+
// controlled abort may let the provider run settle before its graceful
|
|
297
|
+
// cleanup promise does, so prove that shared settlement before publishing
|
|
298
|
+
// dismissed, failed-for-another-reason, or completed truth.
|
|
299
|
+
const gracefulResult = shared.gracefulAbort
|
|
300
|
+
? await shared.gracefulAbort.then(() => ({ ok: true }), (error) => ({ ok: false, error }))
|
|
301
|
+
: undefined;
|
|
302
|
+
if (gracefulResult && !gracefulResult.ok && !transportFailure) {
|
|
303
|
+
return classifySettlementFailure(shared, state, gracefulResult.error);
|
|
304
|
+
}
|
|
305
|
+
if (policyBudgetExceeded || transportFailure) {
|
|
306
|
+
return classifyFailure(shared, state, completionResult.ok ? undefined : completionResult.error, idleTimedOut, transportFailure, policyBudgetExceeded);
|
|
307
|
+
}
|
|
308
|
+
if (state.failEvent) {
|
|
309
|
+
return failFromFailEvent(shared, state, state.failEvent);
|
|
310
|
+
}
|
|
311
|
+
if (!completionResult.ok) {
|
|
312
|
+
return classifyFailure(shared, state, completionResult.error, idleTimedOut, transportFailure, policyBudgetExceeded);
|
|
313
|
+
}
|
|
314
|
+
if (shared.lifecycleAbort.signal.aborted) {
|
|
315
|
+
return classifyFailure(shared, state, shared.lifecycleAbort.signal.reason, idleTimedOut, transportFailure, policyBudgetExceeded);
|
|
316
|
+
}
|
|
317
|
+
if (!state.doneEvent && !completionOutcome) {
|
|
318
|
+
return failWithError(shared, state, {
|
|
319
|
+
code: "SUBAGENT_EXEC_ERROR",
|
|
320
|
+
message: `${shared.terms.provider} exec ended before completion`,
|
|
321
|
+
timeoutMs: null,
|
|
322
|
+
exitCode: null,
|
|
323
|
+
}, state.finalMessageCandidate);
|
|
324
|
+
}
|
|
325
|
+
const finalMessage = state.doneEvent?.finalMessage ??
|
|
326
|
+
completionOutcome?.finalMessage ??
|
|
327
|
+
state.finalMessageCandidate ??
|
|
328
|
+
renderOutcomeCapturedText(state.outputBuffer).trim();
|
|
329
|
+
if (!state.session) {
|
|
330
|
+
return failWithError(shared, state, {
|
|
331
|
+
code: "SUBAGENT_EXEC_ERROR",
|
|
332
|
+
message: `${shared.terms.provider} exec ended without a session handle`,
|
|
333
|
+
timeoutMs: null,
|
|
334
|
+
exitCode: null,
|
|
335
|
+
}, finalMessage);
|
|
336
|
+
}
|
|
337
|
+
return completeOutcome(state, finalMessage);
|
|
234
338
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// dismissed, failed-for-another-reason, or completed truth.
|
|
241
|
-
const gracefulResult = shared.gracefulAbort
|
|
242
|
-
? await shared.gracefulAbort.then(() => ({ ok: true }), (error) => ({ ok: false, error }))
|
|
243
|
-
: undefined;
|
|
244
|
-
if (gracefulResult && !gracefulResult.ok) {
|
|
245
|
-
return classifySettlementFailure(shared, state, gracefulResult.error);
|
|
246
|
-
}
|
|
247
|
-
if (state.failEvent) {
|
|
248
|
-
return failFromFailEvent(shared, state, state.failEvent);
|
|
249
|
-
}
|
|
250
|
-
if (!completionResult.ok) {
|
|
251
|
-
return classifyFailure(shared, state, completionResult.error, idleTimedOut);
|
|
252
|
-
}
|
|
253
|
-
if (shared.lifecycleAbort.signal.aborted) {
|
|
254
|
-
return classifyFailure(shared, state, shared.lifecycleAbort.signal.reason, idleTimedOut);
|
|
255
|
-
}
|
|
256
|
-
if (!state.doneEvent && !completionOutcome) {
|
|
257
|
-
return failWithError(shared, state, {
|
|
258
|
-
code: "SUBAGENT_EXEC_ERROR",
|
|
259
|
-
message: `${shared.terms.provider} exec ended before completion`,
|
|
260
|
-
timeoutMs: null,
|
|
261
|
-
exitCode: null,
|
|
262
|
-
}, state.finalMessageCandidate);
|
|
263
|
-
}
|
|
264
|
-
const finalMessage = state.doneEvent?.finalMessage ??
|
|
265
|
-
completionOutcome?.finalMessage ??
|
|
266
|
-
state.finalMessageCandidate ??
|
|
267
|
-
renderOutcomeCapturedText(state.outputBuffer).trim();
|
|
268
|
-
const session = state.doneEvent?.session ?? completionOutcome?.session;
|
|
269
|
-
if (!session) {
|
|
270
|
-
return failWithError(shared, state, {
|
|
271
|
-
code: "SUBAGENT_EXEC_ERROR",
|
|
272
|
-
message: `${shared.terms.provider} exec ended without a session handle`,
|
|
273
|
-
timeoutMs: null,
|
|
274
|
-
exitCode: null,
|
|
275
|
-
}, finalMessage);
|
|
276
|
-
}
|
|
277
|
-
return completeOutcome(shared, state, finalMessage, session);
|
|
278
|
-
}
|
|
279
|
-
catch (error) {
|
|
280
|
-
if (error instanceof FlowError) {
|
|
281
|
-
throw error;
|
|
339
|
+
catch (error) {
|
|
340
|
+
if (error instanceof FlowError) {
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
return classifyFailure(shared, state, error, idleTimedOut, transportFailure, policyBudgetExceeded);
|
|
282
344
|
}
|
|
283
|
-
|
|
345
|
+
})();
|
|
346
|
+
providerSettlement.catch(() => undefined);
|
|
347
|
+
try {
|
|
348
|
+
const settled = await Promise.race([
|
|
349
|
+
providerSettlement.then((outcome) => ({ kind: "provider", outcome })),
|
|
350
|
+
policyBudget.then(() => ({ kind: "policy-budget" })),
|
|
351
|
+
]);
|
|
352
|
+
return settled.kind === "provider"
|
|
353
|
+
? settled.outcome
|
|
354
|
+
: classifyFailure(shared, state, undefined, idleTimedOut, transportFailure, true);
|
|
284
355
|
}
|
|
285
356
|
finally {
|
|
286
357
|
if (idleTimeout)
|
|
287
358
|
clearTimeout(idleTimeout);
|
|
359
|
+
if (executionTimeout)
|
|
360
|
+
clearTimeout(executionTimeout);
|
|
288
361
|
shared.lifecycleAbort.signal.removeEventListener("abort", requestLiveAbort);
|
|
289
362
|
shared.terminal = true;
|
|
290
363
|
resolveForceWaiters(shared, undefined);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getProviderRegistration } from "../providers/registry.js";
|
|
2
|
-
export async function startLiveProjection(terms, signal, reviveTerms, onIngress, observer) {
|
|
2
|
+
export async function startLiveProjection(terms, signal, reviveTerms, onIngress, observer, onSessionAdmission) {
|
|
3
3
|
const onSyncCheckpoint = observer
|
|
4
4
|
? (control, checkpoint) => {
|
|
5
5
|
observer.onCheckpoint(control, checkpoint);
|
|
@@ -19,6 +19,7 @@ export async function startLiveProjection(terms, signal, reviveTerms, onIngress,
|
|
|
19
19
|
reviveTerms,
|
|
20
20
|
onSyncCheckpoint,
|
|
21
21
|
onDeliveryOpportunity,
|
|
22
|
+
onSessionAdmission,
|
|
22
23
|
onIngress,
|
|
23
24
|
observer,
|
|
24
25
|
});
|
|
@@ -198,6 +198,7 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
|
|
|
198
198
|
queryActive = true;
|
|
199
199
|
notifyActive();
|
|
200
200
|
let resumedSessionId = sessionId;
|
|
201
|
+
let admittedSessionId;
|
|
201
202
|
let finalMessage;
|
|
202
203
|
let latestAssistantMessageModel;
|
|
203
204
|
// Provider acceptance / pre-terminal retry evidence for no-turn classification.
|
|
@@ -207,7 +208,18 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
|
|
|
207
208
|
const seenToolUses = new Map();
|
|
208
209
|
for await (const event of query) {
|
|
209
210
|
builder.ingress(CLAUDE_AGENT_SDK_PROVIDER, event.type, event, claudeIngressDisposition(event.type), () => {
|
|
210
|
-
|
|
211
|
+
const providerSessionId = extractResumeToken(event);
|
|
212
|
+
resumedSessionId = providerSessionId ?? resumedSessionId;
|
|
213
|
+
if (providerSessionId && providerSessionId !== admittedSessionId) {
|
|
214
|
+
admittedSessionId = providerSessionId;
|
|
215
|
+
options.onSessionAdmission?.({
|
|
216
|
+
session: {
|
|
217
|
+
provider: CLAUDE_AGENT_SDK_PROVIDER,
|
|
218
|
+
version: SUBAGENT_SESSION_VERSION,
|
|
219
|
+
handle: { sessionId: providerSessionId },
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
211
223
|
if (isClaudeApiRetryEvent(event)) {
|
|
212
224
|
lastPreTerminalEvidenceWasRetry = true;
|
|
213
225
|
builder.emit({ type: "warning", warning: mapClaudeApiRetryWarning(event) });
|
|
@@ -179,7 +179,7 @@ export const claudeAgentSdkRegistration = {
|
|
|
179
179
|
return loader ? { ...terms, loader } : terms;
|
|
180
180
|
},
|
|
181
181
|
nativePolicyOwnership: () => null,
|
|
182
|
-
startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onIngress }) => {
|
|
182
|
+
startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onSessionAdmission, onIngress }) => {
|
|
183
183
|
if (terms.provider !== CLAUDE_AGENT_SDK_PROVIDER) {
|
|
184
184
|
throw new Error(`startLive expected ${CLAUDE_AGENT_SDK_PROVIDER}`);
|
|
185
185
|
}
|
|
@@ -197,6 +197,7 @@ export const claudeAgentSdkRegistration = {
|
|
|
197
197
|
systemPrompt: claudeTerms.systemPrompt,
|
|
198
198
|
onSyncCheckpoint,
|
|
199
199
|
onEvidence: claudeTerms.onEvidence,
|
|
200
|
+
onSessionAdmission,
|
|
200
201
|
onIngress,
|
|
201
202
|
initialTurn: claudeTerms.initialTurn,
|
|
202
203
|
loader: claudeTerms.loader,
|
|
@@ -13,6 +13,7 @@ function toCodexAppServerEffort(effort) {
|
|
|
13
13
|
}
|
|
14
14
|
export { codexAppServerSessionSchema } from "./session-schema.js";
|
|
15
15
|
const MINIMUM_CODEX_APP_SERVER_VERSION = { major: 0, minor: 143, patch: 0 };
|
|
16
|
+
const CODEX_APP_SERVER_PROBE_TIMEOUT_MS = 1_000;
|
|
16
17
|
/** Non-interactive defaults for turn/start (faye #316 policy A). */
|
|
17
18
|
export const CODEX_APP_SERVER_TURN_APPROVAL_POLICY = "never";
|
|
18
19
|
export { buildCodexAppServerWorkspaceWriteSandboxPolicy, configOwnsApprovalPolicy, configOwnsSandboxPolicy, } from "./policy.js";
|
|
@@ -172,10 +173,41 @@ export async function startCodexAppServer(prompt, cwd, options = {}) {
|
|
|
172
173
|
void drainPendingTells();
|
|
173
174
|
});
|
|
174
175
|
};
|
|
176
|
+
const probe = async () => {
|
|
177
|
+
if (!server || !activeThreadId) {
|
|
178
|
+
throw createProviderFailure(`${CODEX_APP_SERVER_PROVIDER} session probe has no active thread`, {
|
|
179
|
+
code: "SUBAGENT_TRANSPORT_ERROR",
|
|
180
|
+
timeoutMs: CODEX_APP_SERVER_PROBE_TIMEOUT_MS,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
let timer;
|
|
184
|
+
const deadline = new Promise((_, reject) => {
|
|
185
|
+
timer = setTimeout(() => reject(new Error("session probe timed out")), CODEX_APP_SERVER_PROBE_TIMEOUT_MS);
|
|
186
|
+
timer.unref?.();
|
|
187
|
+
});
|
|
188
|
+
try {
|
|
189
|
+
await Promise.race([
|
|
190
|
+
server.request("thread/read", { threadId: activeThreadId }),
|
|
191
|
+
deadline,
|
|
192
|
+
]);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
throw createProviderFailure(`${CODEX_APP_SERVER_PROVIDER} session transport probe failed`, {
|
|
196
|
+
code: "SUBAGENT_TRANSPORT_ERROR",
|
|
197
|
+
timeoutMs: CODEX_APP_SERVER_PROBE_TIMEOUT_MS,
|
|
198
|
+
cause: error,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
if (timer)
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
175
206
|
return createProjectionDriver({
|
|
176
207
|
steerMode: "mid-execution",
|
|
177
208
|
initialTurn: options.initialTurn,
|
|
178
209
|
enqueue,
|
|
210
|
+
probe,
|
|
179
211
|
onIngress: options.onIngress,
|
|
180
212
|
async abort(mode) {
|
|
181
213
|
interruptRequested = true;
|
|
@@ -272,6 +304,15 @@ export async function startCodexAppServer(prompt, cwd, options = {}) {
|
|
|
272
304
|
builder.emit({ type: "fail", message });
|
|
273
305
|
throw createProviderFailure(message);
|
|
274
306
|
}
|
|
307
|
+
const admittedSession = {
|
|
308
|
+
provider: "codex-app-server",
|
|
309
|
+
version: SUBAGENT_SESSION_VERSION,
|
|
310
|
+
handle: {
|
|
311
|
+
threadId: activeThreadId,
|
|
312
|
+
...(serverCodexHome ? { codexHome: serverCodexHome } : {}),
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
options.onSessionAdmission?.({ session: admittedSession });
|
|
275
316
|
if (options.onEvidence)
|
|
276
317
|
emitProviderEvidence(options.onEvidence, { type: "session", provider: CODEX_APP_SERVER_PROVIDER, threadId: activeThreadId }, builderDiagnostic);
|
|
277
318
|
// Policy A defaults, but omit when config.* already owns that surface (thread/start config wins).
|
|
@@ -319,17 +360,9 @@ export async function startCodexAppServer(prompt, cwd, options = {}) {
|
|
|
319
360
|
throw createProviderFailure(message);
|
|
320
361
|
}
|
|
321
362
|
// Successful turn/completed is consumption evidence; interrupt ack alone is not terminal proof.
|
|
322
|
-
const session = {
|
|
323
|
-
provider: "codex-app-server",
|
|
324
|
-
version: SUBAGENT_SESSION_VERSION,
|
|
325
|
-
handle: {
|
|
326
|
-
threadId: activeThreadId,
|
|
327
|
-
...(serverCodexHome ? { codexHome: serverCodexHome } : {}),
|
|
328
|
-
},
|
|
329
|
-
};
|
|
330
363
|
const finalMessage = turnState.lastAgentMessage;
|
|
331
|
-
builder.emit({ type: "done", finalMessage, session });
|
|
332
|
-
return { finalMessage, session };
|
|
364
|
+
builder.emit({ type: "done", finalMessage, session: admittedSession });
|
|
365
|
+
return { finalMessage, session: admittedSession };
|
|
333
366
|
}
|
|
334
367
|
finally {
|
|
335
368
|
activeTurnCompletion = undefined;
|
|
@@ -22,6 +22,11 @@ export function codexAppServerNotificationDisposition(method, params) {
|
|
|
22
22
|
return "acknowledged-unpersisted";
|
|
23
23
|
if (CODEX_APP_SERVER_NOTIFICATION_MAPPED.has(method))
|
|
24
24
|
return "mapped";
|
|
25
|
+
if (method === "item/started") {
|
|
26
|
+
const item = appServerRecord(appServerRecord(params)?.item);
|
|
27
|
+
if (item && typeof item.type === "string" && isCodexAppServerActivityItemType(item.type))
|
|
28
|
+
return "mapped";
|
|
29
|
+
}
|
|
25
30
|
if (method === "account/rateLimits/updated" && codexAppServerRateLimitCause(params))
|
|
26
31
|
return "mapped";
|
|
27
32
|
return "unknown";
|
|
@@ -303,6 +308,24 @@ export function createCodexAppServerTurnMonitor(options) {
|
|
|
303
308
|
});
|
|
304
309
|
}
|
|
305
310
|
}
|
|
311
|
+
if (notification.method === "item/started") {
|
|
312
|
+
const item = notification.params?.item;
|
|
313
|
+
if (!item)
|
|
314
|
+
throw new Error(`${CODEX_APP_SERVER_PROVIDER} item/started lacked required item`);
|
|
315
|
+
const id = coerceString(item.id);
|
|
316
|
+
const itemType = coerceString(item.type);
|
|
317
|
+
if (!id || !itemType)
|
|
318
|
+
throw new Error(`${CODEX_APP_SERVER_PROVIDER} item/started lacked required item id or type`);
|
|
319
|
+
if (isCodexAppServerActivityItemType(itemType)) {
|
|
320
|
+
builder.emit({
|
|
321
|
+
type: "activity",
|
|
322
|
+
phase: "started",
|
|
323
|
+
id,
|
|
324
|
+
tool: itemType,
|
|
325
|
+
call: appServerActivityCall(itemType, item),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
306
329
|
if (notification.method === "turn/started") {
|
|
307
330
|
const turn = notification.params?.turn;
|
|
308
331
|
const turnId = recordActiveTurnId(state, turn?.id, "notification");
|
|
@@ -212,7 +212,7 @@ export const codexAppServerRegistration = {
|
|
|
212
212
|
webSearch: Object.freeze(webSearch),
|
|
213
213
|
});
|
|
214
214
|
},
|
|
215
|
-
startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onDeliveryOpportunity, onIngress, }) => {
|
|
215
|
+
startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onDeliveryOpportunity, onSessionAdmission, onIngress, }) => {
|
|
216
216
|
if (terms.provider !== CODEX_APP_SERVER_PROVIDER) {
|
|
217
217
|
throw new Error(`startLive expected ${CODEX_APP_SERVER_PROVIDER}`);
|
|
218
218
|
}
|
|
@@ -231,6 +231,7 @@ export const codexAppServerRegistration = {
|
|
|
231
231
|
onSyncCheckpoint,
|
|
232
232
|
onDeliveryOpportunity,
|
|
233
233
|
onEvidence: codexTerms.onEvidence,
|
|
234
|
+
onSessionAdmission,
|
|
234
235
|
onIngress,
|
|
235
236
|
initialTurn: codexTerms.initialTurn,
|
|
236
237
|
};
|
|
@@ -264,6 +264,13 @@ export async function startOpencode(prompt, cwd, options = {}) {
|
|
|
264
264
|
builder.emit({ type: "fail", message });
|
|
265
265
|
throw createProviderFailure(message);
|
|
266
266
|
}
|
|
267
|
+
options.onSessionAdmission?.({
|
|
268
|
+
session: {
|
|
269
|
+
provider: OPENCODE_SDK_PROVIDER,
|
|
270
|
+
version: SUBAGENT_SESSION_VERSION,
|
|
271
|
+
handle: { sessionId: activeSessionId },
|
|
272
|
+
},
|
|
273
|
+
});
|
|
267
274
|
idleMonitor = new OpencodeIdleMonitor(sdk.client.v2.session, activeSessionId, combined.signal);
|
|
268
275
|
eventController = createOpencodeEventController({
|
|
269
276
|
builder,
|
|
@@ -104,7 +104,7 @@ export const opencodeSdkRegistration = {
|
|
|
104
104
|
return loader ? { ...terms, loader } : terms;
|
|
105
105
|
},
|
|
106
106
|
nativePolicyOwnership: () => null,
|
|
107
|
-
startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onDeliveryOpportunity, onIngress, }) => {
|
|
107
|
+
startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onDeliveryOpportunity, onSessionAdmission, onIngress, }) => {
|
|
108
108
|
if (terms.provider !== OPENCODE_SDK_PROVIDER) {
|
|
109
109
|
throw new Error(`startLive expected ${OPENCODE_SDK_PROVIDER}`);
|
|
110
110
|
}
|
|
@@ -118,6 +118,7 @@ export const opencodeSdkRegistration = {
|
|
|
118
118
|
onSyncCheckpoint,
|
|
119
119
|
onDeliveryOpportunity,
|
|
120
120
|
onEvidence: opencodeTerms.onEvidence,
|
|
121
|
+
onSessionAdmission,
|
|
121
122
|
onIngress,
|
|
122
123
|
initialTurn: opencodeTerms.initialTurn,
|
|
123
124
|
loader: opencodeTerms.loader,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FlowError } from "../../../flow-error.js";
|
|
2
2
|
import { appendDebugLog } from "../../../telemetry/debug-log.js";
|
|
3
|
+
import { SUBAGENT_SESSION_VERSION, } from "../../session.js";
|
|
3
4
|
import { createProjectionDriver } from "../../harness/execution-handle.js";
|
|
4
5
|
import { createProviderFailure, toProviderTerminalError, } from "../../harness/provider-adapter.js";
|
|
5
6
|
import { createAbortError, emitProviderEvidence, sha256Utf8 } from "../../harness/runtime.js";
|
|
@@ -103,6 +104,15 @@ async function openPiProjectionDriver(prompt, cwd, options) {
|
|
|
103
104
|
...(resourceLoader ? { resourceLoader } : {}),
|
|
104
105
|
});
|
|
105
106
|
const session = created.session;
|
|
107
|
+
if (session.sessionFile) {
|
|
108
|
+
options.onSessionAdmission?.({
|
|
109
|
+
session: {
|
|
110
|
+
provider: PI_PROVIDER,
|
|
111
|
+
version: SUBAGENT_SESSION_VERSION,
|
|
112
|
+
handle: { sessionId: session.sessionId, sessionFile: session.sessionFile },
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
106
116
|
if (options.onEvidence)
|
|
107
117
|
emitProviderEvidence(options.onEvidence, { type: "session", provider: PI_PROVIDER, sessionId: session.sessionId, ...(session.sessionFile ? { sessionFile: session.sessionFile } : {}) }, reportEvidenceDiagnostic);
|
|
108
118
|
session.setSteeringMode("one-at-a-time");
|
|
@@ -142,14 +142,15 @@ function piUsageBody(event) {
|
|
|
142
142
|
if (message?.role !== "assistant")
|
|
143
143
|
return undefined;
|
|
144
144
|
const usage = message.usage;
|
|
145
|
-
if (!usage || typeof usage.input !== "number" ||
|
|
145
|
+
if (!usage || typeof usage.input !== "number" || !Number.isFinite(usage.input)
|
|
146
|
+
|| typeof usage.output !== "number" || !Number.isFinite(usage.output))
|
|
146
147
|
return undefined;
|
|
147
148
|
return {
|
|
148
149
|
type: "usage",
|
|
149
150
|
inputTokens: usage.input,
|
|
150
151
|
outputTokens: usage.output,
|
|
151
|
-
...(typeof usage.cacheRead === "number" ? { cachedInputTokens: usage.cacheRead } : {}),
|
|
152
|
-
...(typeof usage.cost?.total === "number" ? { costUsd: usage.cost.total } : {}),
|
|
152
|
+
...(typeof usage.cacheRead === "number" && Number.isFinite(usage.cacheRead) ? { cachedInputTokens: usage.cacheRead } : {}),
|
|
153
|
+
...(typeof usage.cost?.total === "number" && Number.isFinite(usage.cost.total) ? { costUsd: usage.cost.total } : {}),
|
|
153
154
|
...(typeof message.responseModel === "string"
|
|
154
155
|
? { model: message.responseModel }
|
|
155
156
|
: typeof message.model === "string"
|