@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 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 gated = toolCalls.filter((call) => mode.needsApproval(call.name));
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/prism-harness",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Durable agent sessions for TypeScript — threads, session state and store drivers.",
5
5
  "type": "module",
6
6
  "sideEffects": false,