@particle-academy/prism-harness 0.7.0 → 0.8.0
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 +9 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +11 -0
- package/dist/runtime.js +10 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -202,6 +202,15 @@ reporting a constraint it did not actually check. The same rules as the PHP
|
|
|
202
202
|
reference, message for message, so a document refused in one language is refused
|
|
203
203
|
in the other for the same stated reason.
|
|
204
204
|
|
|
205
|
+
## Failed model steps
|
|
206
|
+
|
|
207
|
+
A client response with `finishReason: 'error'` or `'unknown'` ends the run with
|
|
208
|
+
`HarnessError.code === 'model_step_failed'`. Its `finishReason` identifies which
|
|
209
|
+
of those two finishes occurred; the error message contains no model output.
|
|
210
|
+
The step, cost, and assistant response are recorded before the error is raised.
|
|
211
|
+
Tools requested by that response are refused, with no pending approval authority,
|
|
212
|
+
so approving or resuming later cannot execute them. A new turn can still be sent.
|
|
213
|
+
|
|
205
214
|
## Approvals
|
|
206
215
|
|
|
207
216
|
A mode names the tools a person must approve:
|
package/dist/errors.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export type HarnessErrorCode =
|
|
|
18
18
|
| 'tool_not_available'
|
|
19
19
|
/** A run was refused — by budget, by depth, or by a cancelled ledger. */
|
|
20
20
|
| 'run_not_permitted'
|
|
21
|
+
/** A model returned a terminal error/unknown finish; its tools must not run. */
|
|
22
|
+
| 'model_step_failed'
|
|
21
23
|
/** A stored payload could not be mapped back into a value object. */
|
|
22
24
|
| 'unmappable_content'
|
|
23
25
|
/** A session was asked to do something that needs a runtime it does not have. */
|
|
@@ -75,6 +77,7 @@ export type HarnessErrorCode =
|
|
|
75
77
|
| 'structured_schema_violation';
|
|
76
78
|
export interface HarnessErrorOptions {
|
|
77
79
|
cause?: unknown;
|
|
80
|
+
finishReason?: 'error' | 'unknown';
|
|
78
81
|
/** The model's own text, on a structured failure. See `HarnessError.document`. */
|
|
79
82
|
document?: string;
|
|
80
83
|
/** Every way a document missed its schema, on a structured failure. */
|
|
@@ -82,6 +85,7 @@ export interface HarnessErrorOptions {
|
|
|
82
85
|
}
|
|
83
86
|
export declare class HarnessError extends Error {
|
|
84
87
|
readonly code: HarnessErrorCode;
|
|
88
|
+
readonly finishReason?: 'error' | 'unknown';
|
|
85
89
|
/**
|
|
86
90
|
* The model's own text, on a structured failure.
|
|
87
91
|
*
|
|
@@ -106,6 +110,7 @@ export declare class HarnessError extends Error {
|
|
|
106
110
|
static unknownStoreDriver(slot: string, driver: string): HarnessError;
|
|
107
111
|
static toolNotAvailable(name: string, available: readonly string[]): HarnessError;
|
|
108
112
|
static runNotPermitted(reason: string): HarnessError;
|
|
113
|
+
static modelStepFailed(finishReason: 'error' | 'unknown'): HarnessError;
|
|
109
114
|
static unmappableContent(description: string, options?: HarnessErrorOptions): HarnessError;
|
|
110
115
|
static modeNotConfigured(name: string): HarnessError;
|
|
111
116
|
static modeMalformed(name: string, detail: string): HarnessError;
|
package/dist/errors.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export class HarnessError extends Error {
|
|
2
2
|
code;
|
|
3
|
+
finishReason;
|
|
3
4
|
/**
|
|
4
5
|
* The model's own text, on a structured failure.
|
|
5
6
|
*
|
|
@@ -14,6 +15,10 @@ export class HarnessError extends Error {
|
|
|
14
15
|
super(message, options);
|
|
15
16
|
this.name = 'HarnessError';
|
|
16
17
|
this.code = code;
|
|
18
|
+
if (options.finishReason !== undefined && options.finishReason !== 'error' && options.finishReason !== 'unknown') {
|
|
19
|
+
throw new TypeError('Model step finish reason must be error or unknown');
|
|
20
|
+
}
|
|
21
|
+
this.finishReason = options.finishReason;
|
|
17
22
|
this.document = options.document;
|
|
18
23
|
this.problems = options.problems ?? [];
|
|
19
24
|
}
|
|
@@ -44,6 +49,12 @@ export class HarnessError extends Error {
|
|
|
44
49
|
static runNotPermitted(reason) {
|
|
45
50
|
return new HarnessError('run_not_permitted', reason);
|
|
46
51
|
}
|
|
52
|
+
static modelStepFailed(finishReason) {
|
|
53
|
+
if (finishReason !== 'error' && finishReason !== 'unknown') {
|
|
54
|
+
throw new TypeError('Model step finish reason must be error or unknown');
|
|
55
|
+
}
|
|
56
|
+
return new HarnessError('model_step_failed', `Model step failed: ${finishReason}.`, { finishReason });
|
|
57
|
+
}
|
|
47
58
|
static unmappableContent(description, options = {}) {
|
|
48
59
|
return new HarnessError('unmappable_content', `Could not rebuild stored content: ${description}.`, options);
|
|
49
60
|
}
|
package/dist/runtime.js
CHANGED
|
@@ -171,7 +171,8 @@ export class AgentRuntime {
|
|
|
171
171
|
text = response.text;
|
|
172
172
|
finishReason = response.finishReason;
|
|
173
173
|
const toolCalls = response.toolCalls ?? [];
|
|
174
|
-
const
|
|
174
|
+
const failedFinish = finishReason === 'error' || finishReason === 'unknown' ? finishReason : null;
|
|
175
|
+
const gated = failedFinish === null ? toolCalls.filter((call) => mode.needsApproval(call.name)) : [];
|
|
175
176
|
const requests = gated.map((call) => ({
|
|
176
177
|
approval_id: `apr_${randomUUID().replaceAll('-', '')}`,
|
|
177
178
|
tool_call_id: call.id,
|
|
@@ -184,6 +185,14 @@ export class AgentRuntime {
|
|
|
184
185
|
? { ...(response.additionalContent ?? {}), structured: response.structured ?? null }
|
|
185
186
|
: response.additionalContent;
|
|
186
187
|
await thread.record([assistantRow(response.text, toolCalls, metadata, requests)], runId);
|
|
188
|
+
if (failedFinish !== null) {
|
|
189
|
+
// Complete the transcript with refusals, never approval requests, so a
|
|
190
|
+
// later resume cannot dispatch calls from this failed response.
|
|
191
|
+
if (toolCalls.length > 0) {
|
|
192
|
+
await thread.record([toolResultRow(toolCalls.map(call => toolResultEntry(call, `Not run: model step failed (${failedFinish}).`)))], runId);
|
|
193
|
+
}
|
|
194
|
+
throw HarnessError.modelStepFailed(failedFinish);
|
|
195
|
+
}
|
|
187
196
|
if (toolCalls.length === 0) {
|
|
188
197
|
if (schema !== undefined) {
|
|
189
198
|
// Recorded first, then checked: the exchange happened either way, and
|