@purista/harness 1.2.1 → 1.2.3
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/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.js +276 -141
- package/dist/errors/catalog.d.ts +4 -3
- package/dist/harness/defineHarness.d.ts +45 -4
- package/dist/harness/defineHarness.js +51 -2
- package/dist/index.d.ts +1 -1
- package/dist/memory/sandbox/index.js +7 -1
- package/dist/models/registry.d.ts +10 -3
- package/dist/models/registry.js +45 -3
- package/dist/ports/base-model-provider.js +2 -0
- package/dist/ports/capabilities.d.ts +2 -0
- package/dist/ports/harness-context.d.ts +1 -0
- package/dist/ports/model-provider.d.ts +4 -0
- package/dist/ports/state.d.ts +6 -0
- package/dist/runtime/abort.d.ts +5 -0
- package/dist/runtime/abort.js +33 -0
- package/dist/runtime/durable.d.ts +2 -0
- package/dist/runtime/durable.js +6 -2
- package/dist/runtime/sessionDurable.d.ts +49 -0
- package/dist/runtime/sessionDurable.js +135 -0
- package/dist/runtime/steps.d.ts +19 -1
- package/dist/runtime/steps.js +21 -3
- package/dist/sandbox/index.d.ts +34 -0
- package/dist/sandbox/index.js +40 -3
- package/dist/sessions/index.d.ts +15 -2
- package/dist/sessions/index.js +336 -105
- package/dist/skills/index.js +19 -6
- package/dist/state/in-memory.d.ts +1 -0
- package/dist/state/in-memory.js +15 -0
- package/dist/telemetry/shim.js +9 -4
- package/dist/testing/durableWorkspaceStoreContract.d.ts +1 -1
- package/dist/testing/durableWorkspaceStoreContract.js +64 -28
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +15 -1
- package/dist/tools/mcp/runner.js +11 -6
- package/dist/tools/mcp/stdio.js +170 -1
- package/dist/ulid/index.d.ts +6 -1
- package/dist/ulid/index.js +31 -13
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/dist/workflows/index.js +7 -1
- package/dist/workspace/in-memory.d.ts +9 -10
- package/dist/workspace/in-memory.js +191 -48
- package/package.json +1 -1
- package/dist/harness/errors.d.ts +0 -62
- package/dist/harness/errors.js +0 -67
|
@@ -14,7 +14,7 @@ import { type Sandbox } from '../sandbox/index.js';
|
|
|
14
14
|
import type { ModelHandle } from '../models/registry.js';
|
|
15
15
|
import { type AdapterCapability, type DurableRuntimeAdapter, type HarnessInspection } from '../ports/capabilities.js';
|
|
16
16
|
/** Stable harness version string for diagnostics and generated documentation. */
|
|
17
|
-
export
|
|
17
|
+
export { HARNESS_VERSION } from '../version.js';
|
|
18
18
|
/** OpenTelemetry capture controls used by the harness. */
|
|
19
19
|
export type TelemetryFlavor = 'dual' | 'gen_ai_only' | 'openinference_only';
|
|
20
20
|
export type ContentCaptureMode = 'NO_CONTENT' | 'SPAN_ONLY' | 'EVENT_ONLY' | 'SPAN_AND_EVENT';
|
|
@@ -36,6 +36,8 @@ export interface HarnessDefaults {
|
|
|
36
36
|
skillTimeoutMs?: number;
|
|
37
37
|
/** Per-model timeout in milliseconds. Default: `300_000`. */
|
|
38
38
|
modelTimeoutMs?: number;
|
|
39
|
+
/** Maximum tool calls from one model response executed at the same time. Default: `8`. */
|
|
40
|
+
maxParallelToolCalls?: number;
|
|
39
41
|
/**
|
|
40
42
|
* Max non-system messages forwarded into model calls.
|
|
41
43
|
* `undefined` keeps all history, `0` keeps only system messages.
|
|
@@ -47,6 +49,17 @@ export interface HarnessOptions {
|
|
|
47
49
|
/** Optional harness name for logs, telemetry, and diagnostics. Default: `agent-harness`. */
|
|
48
50
|
name?: string;
|
|
49
51
|
}
|
|
52
|
+
/** Durable execution opt-in for a single workflow call. */
|
|
53
|
+
export interface DurableInvokeOptions {
|
|
54
|
+
/** Stable run id reused across resumes/retries. Matches `/^[A-Za-z0-9_.:-]{1,200}$/`. */
|
|
55
|
+
runId: string;
|
|
56
|
+
/** Worker/process id owning the durable lease. Defaults to the harness worker id. */
|
|
57
|
+
workerId?: string;
|
|
58
|
+
/** Initial durable step id label. Defaults to the workflow id. */
|
|
59
|
+
stepId?: string;
|
|
60
|
+
/** Optional attempt hint; the runtime may raise it on retry. */
|
|
61
|
+
attempt?: number;
|
|
62
|
+
}
|
|
50
63
|
/** Shared invoke options for workflow and agent execution. */
|
|
51
64
|
export interface InvokeOptions {
|
|
52
65
|
/** Abort signal used to cooperatively cancel the call. */
|
|
@@ -61,6 +74,12 @@ export interface InvokeOptions {
|
|
|
61
74
|
tracestate?: string;
|
|
62
75
|
/** Scalar metadata exposed to handlers and telemetry sanitizers. */
|
|
63
76
|
metadata?: Record<string, JsonValue>;
|
|
77
|
+
/**
|
|
78
|
+
* Opt a workflow run into durable execution against the configured
|
|
79
|
+
* `.runtime(...)` (and optional `.workspaceStore(...)`). Workflow-only;
|
|
80
|
+
* supplying it on an agent run throws `ValidationError`.
|
|
81
|
+
*/
|
|
82
|
+
durable?: DurableInvokeOptions;
|
|
64
83
|
}
|
|
65
84
|
/** Canonical built-in tool names provided by the harness. */
|
|
66
85
|
export type BuiltinToolName = 'bash' | 'read' | 'write' | 'edit' | 'glob' | 'grep' | 'list';
|
|
@@ -330,6 +349,12 @@ export interface WorkflowContext<S extends BuilderState, I, O> {
|
|
|
330
349
|
metadata: Readonly<Record<string, JsonValue>>;
|
|
331
350
|
memory: MemoryFacade;
|
|
332
351
|
metrics: Metrics;
|
|
352
|
+
/**
|
|
353
|
+
* Runs `fn` as a durable step. Under a durable invocation the output is
|
|
354
|
+
* checkpointed and replayed on resume without re-running `fn`; otherwise it is
|
|
355
|
+
* a transparent pass-through. See spec 10 "Durable steps".
|
|
356
|
+
*/
|
|
357
|
+
step<T extends JsonValue>(stepId: string, fn: () => Promise<T>): Promise<T>;
|
|
333
358
|
output?: O;
|
|
334
359
|
}
|
|
335
360
|
/** Full context passed to custom agent handlers. */
|
|
@@ -527,7 +552,15 @@ export interface RunSummary {
|
|
|
527
552
|
agentCalls: number;
|
|
528
553
|
error?: SerializedError;
|
|
529
554
|
}
|
|
530
|
-
/**
|
|
555
|
+
/**
|
|
556
|
+
* Harness streaming events emitted from `session.workflows.<id>.stream(...)`.
|
|
557
|
+
*
|
|
558
|
+
* `text(...)` and `object(...)` model calls return final results and do not
|
|
559
|
+
* expose partial output. Consumed model streams are private by default.
|
|
560
|
+
* `model.delta`, `model.object.partial`, and streamed `model.object` are
|
|
561
|
+
* emitted only when that `textStream(...)` or `objectStream(...)` call passes
|
|
562
|
+
* `{ emitRunEvents: true }`.
|
|
563
|
+
*/
|
|
531
564
|
export type RunEvent = {
|
|
532
565
|
type: 'run.started';
|
|
533
566
|
runId: string;
|
|
@@ -553,7 +586,10 @@ export type RunEvent = {
|
|
|
553
586
|
} | {
|
|
554
587
|
type: 'model.delta';
|
|
555
588
|
runId: string;
|
|
556
|
-
|
|
589
|
+
streamId: string;
|
|
590
|
+
agentId?: string;
|
|
591
|
+
workflowId?: string;
|
|
592
|
+
modelAlias?: string;
|
|
557
593
|
delta: string;
|
|
558
594
|
} | {
|
|
559
595
|
type: 'tool.started';
|
|
@@ -578,12 +614,18 @@ export type RunEvent = {
|
|
|
578
614
|
} | {
|
|
579
615
|
type: 'model.object.partial';
|
|
580
616
|
runId: string;
|
|
617
|
+
streamId: string;
|
|
581
618
|
agentId?: string;
|
|
619
|
+
workflowId?: string;
|
|
620
|
+
modelAlias?: string;
|
|
582
621
|
partial: JsonValue;
|
|
583
622
|
} | {
|
|
584
623
|
type: 'model.object';
|
|
585
624
|
runId: string;
|
|
586
625
|
agentId?: string;
|
|
626
|
+
workflowId?: string;
|
|
627
|
+
modelAlias?: string;
|
|
628
|
+
streamId?: string;
|
|
587
629
|
object: JsonValue;
|
|
588
630
|
usage?: TokenUsage;
|
|
589
631
|
} | {
|
|
@@ -713,4 +755,3 @@ export interface HarnessBuilder<S extends BuilderState = {}> {
|
|
|
713
755
|
* ```
|
|
714
756
|
*/
|
|
715
757
|
export declare function defineHarness(opts?: HarnessOptions): HarnessBuilder<{}>;
|
|
716
|
-
export {};
|
|
@@ -4,12 +4,13 @@ import { sandboxMemory } from '../memory/sandbox/index.js';
|
|
|
4
4
|
import { validateMemoryAdapter } from '../ports/memory.js';
|
|
5
5
|
import { validateDurableWorkspaceStore } from '../ports/workspace.js';
|
|
6
6
|
import { InMemoryStateStore } from '../state/in-memory.js';
|
|
7
|
-
import { HarnessConfigError } from '../errors/catalog.js';
|
|
7
|
+
import { HarnessConfigError, SkillManifestError } from '../errors/catalog.js';
|
|
8
|
+
import { BUILTIN_TOOL_NAMES } from '../tools/index.js';
|
|
8
9
|
import { autoDetectSandbox } from '../sandbox/index.js';
|
|
9
10
|
import { createSessionHarness } from '../sessions/index.js';
|
|
10
11
|
import { hasAdapterCapabilities, missingCapabilities, uniqueCapabilities } from '../ports/capabilities.js';
|
|
11
12
|
/** Stable harness version string for diagnostics and generated documentation. */
|
|
12
|
-
export
|
|
13
|
+
export { HARNESS_VERSION } from '../version.js';
|
|
13
14
|
class Builder {
|
|
14
15
|
options;
|
|
15
16
|
configured;
|
|
@@ -53,6 +54,9 @@ class Builder {
|
|
|
53
54
|
if (defaults.historyWindow !== undefined && defaults.historyWindow < 0) {
|
|
54
55
|
throw new HarnessConfigError('historyWindow must be >= 0', { reason: 'invalid_defaults', path: 'defaults.historyWindow' });
|
|
55
56
|
}
|
|
57
|
+
if (defaults.maxParallelToolCalls !== undefined && (!Number.isInteger(defaults.maxParallelToolCalls) || defaults.maxParallelToolCalls < 1)) {
|
|
58
|
+
throw new HarnessConfigError('maxParallelToolCalls must be a positive integer', { reason: 'invalid_defaults', path: 'defaults.maxParallelToolCalls' });
|
|
59
|
+
}
|
|
56
60
|
return this.clone({ defaults });
|
|
57
61
|
}
|
|
58
62
|
models(models) {
|
|
@@ -62,6 +66,11 @@ class Builder {
|
|
|
62
66
|
return this.clone({ models });
|
|
63
67
|
}
|
|
64
68
|
tools(tools) {
|
|
69
|
+
for (const id of Object.keys(tools)) {
|
|
70
|
+
if (!/^[a-z][a-z0-9_]*$/.test(id) || id.length > 64) {
|
|
71
|
+
throw new HarnessConfigError('Invalid tool id. Tool ids must match /^[a-z][a-z0-9_]*$/ and be at most 64 characters.', { reason: 'invalid_tool_id', path: `tools.${id}`, id });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
65
74
|
return this.clone({ tools });
|
|
66
75
|
}
|
|
67
76
|
skills(skills) {
|
|
@@ -85,6 +94,7 @@ class Builder {
|
|
|
85
94
|
if (!models || Object.keys(models).length === 0) {
|
|
86
95
|
throw new HarnessConfigError('At least one model alias is required.', { reason: 'missing_models', path: 'models' });
|
|
87
96
|
}
|
|
97
|
+
this.validateToolSkillNamespace();
|
|
88
98
|
const sandbox = this.configured.sandbox ?? autoDetectSandbox();
|
|
89
99
|
const memory = this.configured.memory ?? sandboxMemory();
|
|
90
100
|
validateMemoryAdapter(memory);
|
|
@@ -106,12 +116,15 @@ class Builder {
|
|
|
106
116
|
state: this.configured.state ?? new InMemoryStateStore(),
|
|
107
117
|
sandbox,
|
|
108
118
|
memory,
|
|
119
|
+
...(this.configured.runtime ? { runtime: this.configured.runtime } : {}),
|
|
120
|
+
...(this.configured.workspaceStore ? { workspaceStore: this.configured.workspaceStore } : {}),
|
|
109
121
|
defaults: {
|
|
110
122
|
agentMaxIterations: this.configured.defaults?.agentMaxIterations ?? 16,
|
|
111
123
|
runTimeoutMs: this.configured.defaults?.runTimeoutMs ?? 600_000,
|
|
112
124
|
toolTimeoutMs: this.configured.defaults?.toolTimeoutMs ?? 120_000,
|
|
113
125
|
skillTimeoutMs: this.configured.defaults?.skillTimeoutMs ?? 60_000,
|
|
114
126
|
modelTimeoutMs: this.configured.defaults?.modelTimeoutMs ?? 300_000,
|
|
127
|
+
maxParallelToolCalls: this.configured.defaults?.maxParallelToolCalls ?? 8,
|
|
115
128
|
...(this.configured.defaults?.historyWindow !== undefined ? { historyWindow: this.configured.defaults.historyWindow } : {})
|
|
116
129
|
},
|
|
117
130
|
models,
|
|
@@ -126,6 +139,42 @@ class Builder {
|
|
|
126
139
|
clone(patch) {
|
|
127
140
|
return new Builder(this.options, { ...this.configured, ...patch });
|
|
128
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Tool ids, skill ids, and built-in tool names share one model-facing
|
|
144
|
+
* namespace (spec 08 §6). A custom tool id must not collide with a built-in
|
|
145
|
+
* tool name or a skill id, and a skill id must not collide with a built-in
|
|
146
|
+
* tool name.
|
|
147
|
+
*/
|
|
148
|
+
validateToolSkillNamespace() {
|
|
149
|
+
const toolIds = Object.keys(this.configured.tools ?? {});
|
|
150
|
+
const skillIds = new Set(Object.keys(this.configured.skills ?? {}));
|
|
151
|
+
const builtinNames = new Set(BUILTIN_TOOL_NAMES);
|
|
152
|
+
for (const id of toolIds) {
|
|
153
|
+
if (builtinNames.has(id)) {
|
|
154
|
+
throw new SkillManifestError(`Custom tool id "${id}" collides with a built-in tool name.`, {
|
|
155
|
+
reason: 'reserved_name',
|
|
156
|
+
skill_id: id,
|
|
157
|
+
source: 'tool'
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (skillIds.has(id)) {
|
|
161
|
+
throw new SkillManifestError(`Custom tool id "${id}" collides with a skill id.`, {
|
|
162
|
+
reason: 'reserved_name',
|
|
163
|
+
skill_id: id,
|
|
164
|
+
source: 'tool'
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const id of skillIds) {
|
|
169
|
+
if (builtinNames.has(id)) {
|
|
170
|
+
throw new SkillManifestError(`Skill id "${id}" collides with a built-in tool name.`, {
|
|
171
|
+
reason: 'reserved_name',
|
|
172
|
+
skill_id: id,
|
|
173
|
+
source: 'skill'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
129
178
|
validateAgentSkillReferences(agents) {
|
|
130
179
|
const configuredSkills = new Set(Object.keys(this.configured.skills ?? {}));
|
|
131
180
|
for (const [agentId, agent] of Object.entries(agents)) {
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export * from './telemetry/index.js';
|
|
|
4
4
|
export * from './ulid/index.js';
|
|
5
5
|
export * from './ports/index.js';
|
|
6
6
|
export { createDurableWorkflowContext, DurableStepError, DurableRunLeaseError, DurableTerminalRunError, inMemoryDurableRuntime, isTerminalRunStatus } from './runtime/index.js';
|
|
7
|
-
export type { DurableActiveRunStatus, DurableWorkflowContext, DurableRunLease, DurableRunStart, DurableRunStatus, DurableRuntime, DurableTerminalRunStatus, FinishRunPatch, InMemoryDurableRuntimeOptions, RunCheckpoint } from './runtime/index.js';
|
|
7
|
+
export type { DurableActiveRunStatus, DurableWorkflowContext, DurableWorkflowContextOptions, DurableStepCommit, DurableRunLease, DurableRunStart, DurableRunStatus, DurableRuntime, DurableTerminalRunStatus, FinishRunPatch, InMemoryDurableRuntimeOptions, RunCheckpoint } from './runtime/index.js';
|
|
8
8
|
export * from './state/in-memory.js';
|
|
9
9
|
export * from './models/json.js';
|
|
10
10
|
export type { SessionRecord, Message, RunRecord, PersistedRunEvent, RunStatus } from './models/state.js';
|
|
@@ -45,7 +45,13 @@ class SandboxMemoryAdapter {
|
|
|
45
45
|
const path = `${root}/${key}.json`;
|
|
46
46
|
if (!(await sandbox.exists(path)))
|
|
47
47
|
return undefined;
|
|
48
|
-
|
|
48
|
+
const raw = await sandbox.readText(path);
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(raw);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new StateError('Stored memory value is not valid JSON.', { op: 'memory.get', reason: 'corrupt_value' }, error);
|
|
54
|
+
}
|
|
49
55
|
},
|
|
50
56
|
set: async (key, value, op) => {
|
|
51
57
|
op.signal.throwIfAborted();
|
|
@@ -2,11 +2,18 @@ import type { EmbeddingRequest, EmbeddingResponse, ContentPart, ModelAlias, Mode
|
|
|
2
2
|
import type { TelemetryShim } from '../telemetry/index.js';
|
|
3
3
|
import type { JsonValue } from './json.js';
|
|
4
4
|
export interface ModelInvokeContext {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/** Harness instance name used for telemetry and run-event attribution. */
|
|
6
|
+
harnessName?: string;
|
|
7
|
+
/** Session id used for telemetry and run-event attribution. */
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
/** Run id used for telemetry and run-event attribution. */
|
|
10
|
+
runId?: string;
|
|
11
|
+
/** Workflow id when the model call belongs to a workflow run. */
|
|
8
12
|
workflowId?: string;
|
|
13
|
+
/** Agent id when the model call belongs to an agent run. */
|
|
9
14
|
agentId?: string;
|
|
15
|
+
/** Mirrors consumed stream chunks into the enclosing session `RunEvent` stream. Defaults to false. */
|
|
16
|
+
emitRunEvents?: boolean;
|
|
10
17
|
}
|
|
11
18
|
type TextPart = Extract<ContentPart, {
|
|
12
19
|
kind: 'text';
|
package/dist/models/registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelCapabilityError } from '../errors/index.js';
|
|
1
|
+
import { ModelCapabilityError, ModelError } from '../errors/index.js';
|
|
2
2
|
import { ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_FINISH_REASONS, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOKEN_TYPE, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_TOKEN_TYPE_VALUE_INPUT, GEN_AI_TOKEN_TYPE_VALUE_OUTPUT } from '@opentelemetry/semantic-conventions/incubating';
|
|
3
3
|
/**
|
|
4
4
|
* Creates per-alias model handles that enforce capability gates before provider invocation.
|
|
@@ -92,7 +92,7 @@ function createHandle(aliasKey, alias, options) {
|
|
|
92
92
|
signal,
|
|
93
93
|
traceparent: req.traceparent ?? options.telemetry?.currentTraceparent()
|
|
94
94
|
};
|
|
95
|
-
return withModelSpan(options, aliasKey, alias, 'embeddings', ctx, () => alias.provider.embed(fullReq));
|
|
95
|
+
return withModelSpan(options, aliasKey, alias, 'embeddings', ctx, () => alias.provider.embed(fullReq)).then((response) => validateEmbeddingResponse(aliasKey, alias, fullReq, response));
|
|
96
96
|
},
|
|
97
97
|
rerank(req, signal, ctx) {
|
|
98
98
|
ensureCapabilities(aliasKey, alias, 'rerank', req);
|
|
@@ -107,10 +107,51 @@ function createHandle(aliasKey, alias, options) {
|
|
|
107
107
|
signal,
|
|
108
108
|
traceparent: req.traceparent ?? options.telemetry?.currentTraceparent()
|
|
109
109
|
};
|
|
110
|
-
return withModelSpan(options, aliasKey, alias, 'rerank', ctx, () => alias.provider.rerank(fullReq));
|
|
110
|
+
return withModelSpan(options, aliasKey, alias, 'rerank', ctx, () => alias.provider.rerank(fullReq)).then((response) => validateRerankResponse(aliasKey, alias, fullReq, response));
|
|
111
111
|
}
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Provider-neutral guard: the number of embeddings must match the number of
|
|
116
|
+
* inputs, and indices must cover every input exactly once. Protects callers
|
|
117
|
+
* that associate vectors with inputs by position.
|
|
118
|
+
*/
|
|
119
|
+
function validateEmbeddingResponse(aliasKey, alias, req, response) {
|
|
120
|
+
const expected = Array.isArray(req.input) ? req.input.length : 1;
|
|
121
|
+
const indices = new Set(response.embeddings.map((item) => item.index));
|
|
122
|
+
const validIndices = response.embeddings.every((item) => Number.isInteger(item.index) && item.index >= 0 && item.index < expected);
|
|
123
|
+
if (response.embeddings.length !== expected || indices.size !== expected || !validIndices) {
|
|
124
|
+
throw new ModelError('Embedding response does not match the request input count.', {
|
|
125
|
+
provider: alias.provider.id,
|
|
126
|
+
model: alias.model,
|
|
127
|
+
method: 'embed',
|
|
128
|
+
reason: 'embedding_count_mismatch',
|
|
129
|
+
providerBody: { expected, received: response.embeddings.length, alias: aliasKey }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return response;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Provider-neutral guard: every rerank result must reference a distinct, valid
|
|
136
|
+
* document index, and the count must not exceed the requested document count
|
|
137
|
+
* (or `topN` when supplied).
|
|
138
|
+
*/
|
|
139
|
+
function validateRerankResponse(aliasKey, alias, req, response) {
|
|
140
|
+
const documentCount = req.documents.length;
|
|
141
|
+
const limit = req.topN !== undefined ? Math.min(req.topN, documentCount) : documentCount;
|
|
142
|
+
const indices = new Set(response.results.map((item) => item.index));
|
|
143
|
+
const validIndices = response.results.every((item) => Number.isInteger(item.index) && item.index >= 0 && item.index < documentCount);
|
|
144
|
+
if (response.results.length > limit || indices.size !== response.results.length || !validIndices) {
|
|
145
|
+
throw new ModelError('Rerank response does not map back to the request documents.', {
|
|
146
|
+
provider: alias.provider.id,
|
|
147
|
+
model: alias.model,
|
|
148
|
+
method: 'rerank',
|
|
149
|
+
reason: 'rerank_result_mismatch',
|
|
150
|
+
providerBody: { documentCount, limit, received: response.results.length, alias: aliasKey }
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return response;
|
|
154
|
+
}
|
|
114
155
|
function withModelStreamSpan(options, aliasKey, alias, method, ctx, fn) {
|
|
115
156
|
if (!options.telemetry)
|
|
116
157
|
return fn();
|
|
@@ -306,6 +347,7 @@ function mergeDefaults(alias, call) {
|
|
|
306
347
|
|| merged.maxTokens !== undefined
|
|
307
348
|
|| merged.topP !== undefined
|
|
308
349
|
|| merged.stopSequences !== undefined
|
|
350
|
+
|| merged.parallelToolCalls !== undefined
|
|
309
351
|
|| Object.keys(merged.providerOptions ?? {}).length > 0;
|
|
310
352
|
return hasTopLevel ? merged : undefined;
|
|
311
353
|
}
|
|
@@ -161,6 +161,8 @@ export class BaseModelProvider {
|
|
|
161
161
|
const controller = new AbortController();
|
|
162
162
|
const relay = () => controller.abort(req.signal.reason);
|
|
163
163
|
req.signal.addEventListener('abort', relay, { once: true });
|
|
164
|
+
if (req.signal.aborted)
|
|
165
|
+
relay();
|
|
164
166
|
let rejectTimeout;
|
|
165
167
|
const timeoutPromise = new Promise((_, reject) => { rejectTimeout = reject; });
|
|
166
168
|
const timeout = setTimeout(() => {
|
|
@@ -17,6 +17,8 @@ export type AdapterCapability =
|
|
|
17
17
|
| 'sandbox.resume'
|
|
18
18
|
/** Sandbox can snapshot and release active compute. */
|
|
19
19
|
| 'sandbox.hibernate'
|
|
20
|
+
/** Sandbox can host a long-lived process with streaming stdin/stdout. */
|
|
21
|
+
| 'sandbox.spawn'
|
|
20
22
|
/** Runtime can commit stable checkpoints. */
|
|
21
23
|
| 'runtime.checkpoint'
|
|
22
24
|
/** Runtime can retry durable boundaries. */
|
|
@@ -29,6 +29,8 @@ export interface ModelDefaults {
|
|
|
29
29
|
maxTokens?: number;
|
|
30
30
|
topP?: number;
|
|
31
31
|
stopSequences?: string[];
|
|
32
|
+
/** Whether providers should allow the model to emit multiple independent tool calls in one turn. */
|
|
33
|
+
parallelToolCalls?: boolean;
|
|
32
34
|
providerOptions?: Record<string, unknown>;
|
|
33
35
|
}
|
|
34
36
|
/** Per-call generation overrides. */
|
|
@@ -37,6 +39,8 @@ export interface ModelCallOptions {
|
|
|
37
39
|
maxTokens?: number;
|
|
38
40
|
topP?: number;
|
|
39
41
|
stopSequences?: string[];
|
|
42
|
+
/** Overrides whether providers should allow multiple tool calls in one model turn. */
|
|
43
|
+
parallelToolCalls?: boolean;
|
|
40
44
|
providerOptions?: Record<string, unknown>;
|
|
41
45
|
}
|
|
42
46
|
/** Tool call envelope emitted by model adapters. */
|
package/dist/ports/state.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export interface StateStore {
|
|
|
20
20
|
before?: string;
|
|
21
21
|
}): Promise<Message[]>;
|
|
22
22
|
clearMessages(sessionId: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Atomically replace all messages for a session under one lock (clear +
|
|
25
|
+
* append). Adapters that implement this provide the spec-mandated atomic
|
|
26
|
+
* `replaceHistory`; the session layer falls back to clear+append when absent.
|
|
27
|
+
*/
|
|
28
|
+
replaceMessages?(sessionId: string, messages: Message[]): Promise<void>;
|
|
23
29
|
createRun(record: RunRecord): Promise<void>;
|
|
24
30
|
finishRun(runId: string, patch: FinishRunPatch): Promise<void>;
|
|
25
31
|
getRun(runId: string): Promise<RunRecord | undefined>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { OperationCancelledError, OperationTimeoutError } from '../errors/index.js';
|
|
2
|
+
type AbortScope = 'run' | 'model' | 'tool' | 'workflow' | 'agent' | 'sandbox' | 'memory' | 'workspace';
|
|
3
|
+
export declare function abortError(signal: AbortSignal, scope: AbortScope, message: string): OperationCancelledError | OperationTimeoutError;
|
|
4
|
+
export declare function withAbortSignal<T>(signal: AbortSignal, scope: AbortScope, message: string, fn: () => Promise<T>): Promise<T>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { OperationCancelledError, OperationTimeoutError } from '../errors/index.js';
|
|
2
|
+
export function abortError(signal, scope, message) {
|
|
3
|
+
if (signal.reason instanceof OperationTimeoutError)
|
|
4
|
+
return signal.reason;
|
|
5
|
+
if (signal.reason instanceof OperationCancelledError)
|
|
6
|
+
return signal.reason;
|
|
7
|
+
return new OperationCancelledError(message, { scope }, signal.reason);
|
|
8
|
+
}
|
|
9
|
+
export async function withAbortSignal(signal, scope, message, fn) {
|
|
10
|
+
if (signal.aborted)
|
|
11
|
+
throw abortError(signal, scope, message);
|
|
12
|
+
let abortListener;
|
|
13
|
+
const abortPromise = new Promise((_, reject) => {
|
|
14
|
+
abortListener = () => reject(abortError(signal, scope, message));
|
|
15
|
+
signal.addEventListener('abort', abortListener, { once: true });
|
|
16
|
+
if (signal.aborted)
|
|
17
|
+
abortListener();
|
|
18
|
+
});
|
|
19
|
+
try {
|
|
20
|
+
return await Promise.race([fn(), abortPromise]);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error instanceof OperationCancelledError || error instanceof OperationTimeoutError)
|
|
24
|
+
throw error;
|
|
25
|
+
if (signal.aborted)
|
|
26
|
+
throw abortError(signal, scope, message);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
if (abortListener)
|
|
31
|
+
signal.removeEventListener('abort', abortListener);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -45,6 +45,8 @@ export interface DurableRunLease {
|
|
|
45
45
|
};
|
|
46
46
|
/** Last committed checkpoint, if any. */
|
|
47
47
|
readonly checkpoint?: RunCheckpoint;
|
|
48
|
+
/** All committed checkpoints for this run, in commit order, for step replay. */
|
|
49
|
+
readonly checkpoints?: readonly RunCheckpoint[];
|
|
48
50
|
/** Releases this in-memory lease without making the run terminal. */
|
|
49
51
|
release(): Promise<void>;
|
|
50
52
|
}
|
package/dist/runtime/durable.js
CHANGED
|
@@ -61,7 +61,8 @@ class InMemoryDurableRuntime {
|
|
|
61
61
|
const state = current ?? {
|
|
62
62
|
start: record,
|
|
63
63
|
status: 'running',
|
|
64
|
-
attempt: Math.max(1, record.attempt ?? 1)
|
|
64
|
+
attempt: Math.max(1, record.attempt ?? 1),
|
|
65
|
+
checkpoints: new Map()
|
|
65
66
|
};
|
|
66
67
|
if (current) {
|
|
67
68
|
state.attempt += 1;
|
|
@@ -95,7 +96,9 @@ class InMemoryDurableRuntime {
|
|
|
95
96
|
throw new DurableTerminalRunError(checkpoint.runId, state.status);
|
|
96
97
|
}
|
|
97
98
|
const committedAt = checkpoint.committedAt ?? new Date().toISOString();
|
|
98
|
-
|
|
99
|
+
const stored = { ...checkpoint, committedAt };
|
|
100
|
+
state.checkpoint = stored;
|
|
101
|
+
state.checkpoints.set(stored.stepId, stored);
|
|
99
102
|
this.checkpointCommitCount += 1;
|
|
100
103
|
if (this.options.failAfterCheckpoint === this.checkpointCommitCount) {
|
|
101
104
|
this.releaseLease(lease);
|
|
@@ -148,6 +151,7 @@ class InMemoryDurableRuntime {
|
|
|
148
151
|
attempt: state.attempt
|
|
149
152
|
},
|
|
150
153
|
...(state.checkpoint ? { checkpoint: state.checkpoint } : {}),
|
|
154
|
+
checkpoints: [...state.checkpoints.values()].sort((a, b) => a.sequence - b.sequence),
|
|
151
155
|
release: async () => {
|
|
152
156
|
await this.withSessionLock(lease.sessionId, async () => {
|
|
153
157
|
this.releaseLease(lease);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Logger } from '../logger/index.js';
|
|
2
|
+
import type { JsonValue } from '../models/json.js';
|
|
3
|
+
import type { DurableWorkspaceStore } from '../ports/workspace.js';
|
|
4
|
+
import type { DurableRuntime } from './durable.js';
|
|
5
|
+
import { type DurableWorkflowContext } from './steps.js';
|
|
6
|
+
/** Run-id format accepted for durable invocations. */
|
|
7
|
+
export declare const DURABLE_RUN_ID_PATTERN: RegExp;
|
|
8
|
+
/** Caller-supplied durable invocation options (mirror of `InvokeOptions.durable`). */
|
|
9
|
+
export interface DurableInvokeOptions {
|
|
10
|
+
runId: string;
|
|
11
|
+
workerId?: string;
|
|
12
|
+
stepId?: string;
|
|
13
|
+
attempt?: number;
|
|
14
|
+
}
|
|
15
|
+
/** Durable binding driving one workflow run's lease and workspace lifecycle. */
|
|
16
|
+
export interface DurableWorkflowBinding {
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
readonly attempt: number;
|
|
19
|
+
readonly resumed: boolean;
|
|
20
|
+
readonly step: DurableWorkflowContext['step'];
|
|
21
|
+
/** Marks the run successfully terminal and, when policy permits, cleans up the workspace. */
|
|
22
|
+
finishSuccess(output: JsonValue): Promise<void>;
|
|
23
|
+
/** Marks the run cancelled-terminal and aborts the workspace (blocks resume). */
|
|
24
|
+
finishCancelled(error: unknown): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Releases the lease without making the run terminal when it was not settled,
|
|
27
|
+
* leaving a failed run resumable by a later retry with the same run id.
|
|
28
|
+
*/
|
|
29
|
+
dispose(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** Narrows a configured runtime adapter to an executable durable runtime. */
|
|
32
|
+
export declare function isExecutableDurableRuntime(runtime: unknown): runtime is DurableRuntime;
|
|
33
|
+
/**
|
|
34
|
+
* Acquires a durable runtime lease for a workflow run and, when a workspace
|
|
35
|
+
* store is configured, starts or resumes the durable workspace and links each
|
|
36
|
+
* new step checkpoint to a workspace checkpoint (spec 21 §16.1).
|
|
37
|
+
*/
|
|
38
|
+
export declare function beginDurableWorkflow(args: {
|
|
39
|
+
runtime: DurableRuntime;
|
|
40
|
+
workspaceStore?: DurableWorkspaceStore;
|
|
41
|
+
durable: DurableInvokeOptions;
|
|
42
|
+
defaultWorkerId: string;
|
|
43
|
+
sessionId: string;
|
|
44
|
+
workflowId: string;
|
|
45
|
+
input: JsonValue;
|
|
46
|
+
signal: AbortSignal;
|
|
47
|
+
logger: Logger;
|
|
48
|
+
harnessName: string;
|
|
49
|
+
}): Promise<DurableWorkflowBinding>;
|