@purista/harness 1.0.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/LICENSE +201 -0
- package/README.md +23 -0
- package/dist/agents/index.d.ts +34 -0
- package/dist/agents/index.js +301 -0
- package/dist/errors/catalog.d.ts +185 -0
- package/dist/errors/catalog.js +144 -0
- package/dist/errors/harness-error.d.ts +64 -0
- package/dist/errors/harness-error.js +58 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/redaction.d.ts +5 -0
- package/dist/errors/redaction.js +64 -0
- package/dist/harness/defineHarness.d.ts +640 -0
- package/dist/harness/defineHarness.js +176 -0
- package/dist/harness/errors.d.ts +62 -0
- package/dist/harness/errors.js +67 -0
- package/dist/harness/types.d.ts +27 -0
- package/dist/harness/types.js +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +12 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +2 -0
- package/dist/logger/json-logger.d.ts +31 -0
- package/dist/logger/json-logger.js +65 -0
- package/dist/logger/logger.d.ts +31 -0
- package/dist/logger/logger.js +1 -0
- package/dist/models/json.d.ts +6 -0
- package/dist/models/json.js +1 -0
- package/dist/models/registry.d.ts +112 -0
- package/dist/models/registry.js +286 -0
- package/dist/models/state.d.ts +64 -0
- package/dist/models/state.js +1 -0
- package/dist/ports/base-model-provider.d.ts +56 -0
- package/dist/ports/base-model-provider.js +343 -0
- package/dist/ports/capabilities.d.ts +70 -0
- package/dist/ports/capabilities.js +38 -0
- package/dist/ports/feedback.d.ts +29 -0
- package/dist/ports/feedback.js +1 -0
- package/dist/ports/harness-context.d.ts +20 -0
- package/dist/ports/harness-context.js +1 -0
- package/dist/ports/index.d.ts +6 -0
- package/dist/ports/index.js +6 -0
- package/dist/ports/model-provider.d.ts +280 -0
- package/dist/ports/model-provider.js +1 -0
- package/dist/ports/state.d.ts +72 -0
- package/dist/ports/state.js +24 -0
- package/dist/runtime/durable.d.ts +134 -0
- package/dist/runtime/durable.js +185 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/steps.d.ts +22 -0
- package/dist/runtime/steps.js +51 -0
- package/dist/sandbox/index.d.ts +111 -0
- package/dist/sandbox/index.js +165 -0
- package/dist/sessions/index.d.ts +23 -0
- package/dist/sessions/index.js +718 -0
- package/dist/skills/index.d.ts +8 -0
- package/dist/skills/index.js +88 -0
- package/dist/state/in-memory.d.ts +35 -0
- package/dist/state/in-memory.js +140 -0
- package/dist/telemetry/index.d.ts +1 -0
- package/dist/telemetry/index.js +1 -0
- package/dist/telemetry/shim.d.ts +26 -0
- package/dist/telemetry/shim.js +120 -0
- package/dist/testing/capabilities.d.ts +11 -0
- package/dist/testing/capabilities.js +20 -0
- package/dist/testing/fakeModelProvider.d.ts +25 -0
- package/dist/testing/fakeModelProvider.js +79 -0
- package/dist/testing/feedback.d.ts +10 -0
- package/dist/testing/feedback.js +24 -0
- package/dist/testing/fixtures/mcp/fake-http-server.d.ts +8 -0
- package/dist/testing/fixtures/mcp/fake-http-server.js +95 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.js +11 -0
- package/dist/testing/sandboxContract.d.ts +4 -0
- package/dist/testing/sandboxContract.js +74 -0
- package/dist/testing/sandboxSnapshot.d.ts +7 -0
- package/dist/testing/sandboxSnapshot.js +201 -0
- package/dist/testing/stateStoreContract.d.ts +2 -0
- package/dist/testing/stateStoreContract.js +109 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +123 -0
- package/dist/tools/mcp/http.d.ts +2 -0
- package/dist/tools/mcp/http.js +109 -0
- package/dist/tools/mcp/index.d.ts +2 -0
- package/dist/tools/mcp/index.js +2 -0
- package/dist/tools/mcp/runner.d.ts +74 -0
- package/dist/tools/mcp/runner.js +238 -0
- package/dist/tools/mcp/schema.d.ts +41 -0
- package/dist/tools/mcp/schema.js +251 -0
- package/dist/tools/mcp/stdio.d.ts +2 -0
- package/dist/tools/mcp/stdio.js +122 -0
- package/dist/ulid/index.d.ts +6 -0
- package/dist/ulid/index.js +35 -0
- package/dist/workflows/index.d.ts +8 -0
- package/dist/workflows/index.js +26 -0
- package/package.json +75 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { ModelCapabilityError } from '../errors/index.js';
|
|
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
|
+
/**
|
|
4
|
+
* Creates per-alias model handles that enforce capability gates before provider invocation.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const registry = createModelRegistry({
|
|
9
|
+
* assistant: { provider, model: 'gpt-4.1-mini', capabilities: ['text'] }
|
|
10
|
+
* })
|
|
11
|
+
* const out = await registry.assistant.text({ messages: [{ role: 'user', content: 'hi' }] }, new AbortController().signal)
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function createModelRegistry(aliases, options = {}) {
|
|
15
|
+
return Object.fromEntries(Object.entries(aliases).map(([aliasKey, alias]) => [aliasKey, createHandle(aliasKey, alias, options)]));
|
|
16
|
+
}
|
|
17
|
+
function createHandle(aliasKey, alias, options) {
|
|
18
|
+
return {
|
|
19
|
+
text(req, signal, ctx) {
|
|
20
|
+
ensureCapabilities(aliasKey, alias, 'text', req);
|
|
21
|
+
if (!alias.provider.text)
|
|
22
|
+
throw methodMissing(aliasKey, 'text');
|
|
23
|
+
const fullReq = {
|
|
24
|
+
model: alias.model,
|
|
25
|
+
messages: req.messages,
|
|
26
|
+
...(req.call ? { call: req.call } : {}),
|
|
27
|
+
...(mergeDefaults(alias, req.call) ? { defaults: mergeDefaults(alias, req.call) } : {}),
|
|
28
|
+
...(req.tools ? { tools: req.tools } : {}),
|
|
29
|
+
signal,
|
|
30
|
+
traceparent: req.traceparent ?? options.telemetry?.currentTraceparent()
|
|
31
|
+
};
|
|
32
|
+
return withModelSpan(options, aliasKey, alias, 'text', ctx, () => alias.provider.text(fullReq));
|
|
33
|
+
},
|
|
34
|
+
textStream(req, signal, ctx) {
|
|
35
|
+
ensureCapabilities(aliasKey, alias, 'text_stream', req);
|
|
36
|
+
if (!alias.provider.textStream)
|
|
37
|
+
throw methodMissing(aliasKey, 'textStream');
|
|
38
|
+
const fullReq = {
|
|
39
|
+
model: alias.model,
|
|
40
|
+
messages: req.messages,
|
|
41
|
+
...(req.call ? { call: req.call } : {}),
|
|
42
|
+
...(mergeDefaults(alias, req.call) ? { defaults: mergeDefaults(alias, req.call) } : {}),
|
|
43
|
+
...(req.tools ? { tools: req.tools } : {}),
|
|
44
|
+
signal,
|
|
45
|
+
traceparent: req.traceparent ?? options.telemetry?.currentTraceparent()
|
|
46
|
+
};
|
|
47
|
+
return withModelStreamSpan(options, aliasKey, alias, 'text_stream', ctx, () => alias.provider.textStream(fullReq));
|
|
48
|
+
},
|
|
49
|
+
object(req, signal, ctx) {
|
|
50
|
+
ensureCapabilities(aliasKey, alias, 'object', req);
|
|
51
|
+
if (!alias.provider.object)
|
|
52
|
+
throw methodMissing(aliasKey, 'object');
|
|
53
|
+
const fullReq = {
|
|
54
|
+
model: alias.model,
|
|
55
|
+
messages: req.messages,
|
|
56
|
+
...(req.call ? { call: req.call } : {}),
|
|
57
|
+
...(mergeDefaults(alias, req.call) ? { defaults: mergeDefaults(alias, req.call) } : {}),
|
|
58
|
+
...(req.tools ? { tools: req.tools } : {}),
|
|
59
|
+
schema: req.schema,
|
|
60
|
+
...(req.schemaName ? { schemaName: req.schemaName } : {}),
|
|
61
|
+
signal,
|
|
62
|
+
traceparent: req.traceparent ?? options.telemetry?.currentTraceparent()
|
|
63
|
+
};
|
|
64
|
+
return withModelSpan(options, aliasKey, alias, 'object', ctx, () => alias.provider.object(fullReq));
|
|
65
|
+
},
|
|
66
|
+
objectStream(req, signal, ctx) {
|
|
67
|
+
ensureCapabilities(aliasKey, alias, 'object_stream', req);
|
|
68
|
+
if (!alias.provider.objectStream)
|
|
69
|
+
throw methodMissing(aliasKey, 'objectStream');
|
|
70
|
+
const fullReq = {
|
|
71
|
+
model: alias.model,
|
|
72
|
+
messages: req.messages,
|
|
73
|
+
...(req.call ? { call: req.call } : {}),
|
|
74
|
+
...(mergeDefaults(alias, req.call) ? { defaults: mergeDefaults(alias, req.call) } : {}),
|
|
75
|
+
...(req.tools ? { tools: req.tools } : {}),
|
|
76
|
+
schema: req.schema,
|
|
77
|
+
...(req.schemaName ? { schemaName: req.schemaName } : {}),
|
|
78
|
+
signal,
|
|
79
|
+
traceparent: req.traceparent ?? options.telemetry?.currentTraceparent()
|
|
80
|
+
};
|
|
81
|
+
return withModelStreamSpan(options, aliasKey, alias, 'object_stream', ctx, () => alias.provider.objectStream(fullReq));
|
|
82
|
+
},
|
|
83
|
+
embed(req, signal, ctx) {
|
|
84
|
+
ensureCapabilities(aliasKey, alias, 'embeddings', req);
|
|
85
|
+
if (!alias.provider.embed)
|
|
86
|
+
throw methodMissing(aliasKey, 'embed');
|
|
87
|
+
const fullReq = {
|
|
88
|
+
model: alias.model,
|
|
89
|
+
input: req.input,
|
|
90
|
+
...(req.dimensions !== undefined ? { dimensions: req.dimensions } : {}),
|
|
91
|
+
...(req.call ? { call: req.call } : {}),
|
|
92
|
+
signal,
|
|
93
|
+
traceparent: req.traceparent ?? options.telemetry?.currentTraceparent()
|
|
94
|
+
};
|
|
95
|
+
return withModelSpan(options, aliasKey, alias, 'embeddings', ctx, () => alias.provider.embed(fullReq));
|
|
96
|
+
},
|
|
97
|
+
rerank(req, signal, ctx) {
|
|
98
|
+
ensureCapabilities(aliasKey, alias, 'rerank', req);
|
|
99
|
+
if (!alias.provider.rerank)
|
|
100
|
+
throw methodMissing(aliasKey, 'rerank');
|
|
101
|
+
const fullReq = {
|
|
102
|
+
model: alias.model,
|
|
103
|
+
query: req.query,
|
|
104
|
+
documents: req.documents,
|
|
105
|
+
...(req.topN !== undefined ? { topN: req.topN } : {}),
|
|
106
|
+
...(req.call ? { call: req.call } : {}),
|
|
107
|
+
signal,
|
|
108
|
+
traceparent: req.traceparent ?? options.telemetry?.currentTraceparent()
|
|
109
|
+
};
|
|
110
|
+
return withModelSpan(options, aliasKey, alias, 'rerank', ctx, () => alias.provider.rerank(fullReq));
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function withModelStreamSpan(options, aliasKey, alias, method, ctx, fn) {
|
|
115
|
+
if (!options.telemetry)
|
|
116
|
+
return fn();
|
|
117
|
+
const started = Date.now();
|
|
118
|
+
const attrs = modelSpanAttrs(options, aliasKey, alias, method, ctx);
|
|
119
|
+
return streamWithTelemetry(options.telemetry, `chat ${alias.model}`, attrs, async function* (span) {
|
|
120
|
+
let lastUsage;
|
|
121
|
+
let lastFinishReason;
|
|
122
|
+
for await (const chunk of fn()) {
|
|
123
|
+
const current = chunk;
|
|
124
|
+
if (current.usage)
|
|
125
|
+
lastUsage = current.usage;
|
|
126
|
+
if (current.finishReason)
|
|
127
|
+
lastFinishReason = current.finishReason;
|
|
128
|
+
yield chunk;
|
|
129
|
+
}
|
|
130
|
+
if (lastUsage) {
|
|
131
|
+
span.setAttributes({
|
|
132
|
+
[ATTR_GEN_AI_USAGE_INPUT_TOKENS]: lastUsage.inputTokens,
|
|
133
|
+
[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: lastUsage.outputTokens,
|
|
134
|
+
'gen_ai.usage.total_tokens': lastUsage.totalTokens
|
|
135
|
+
});
|
|
136
|
+
options.telemetry?.recordCounter('gen_ai.client.token.usage', lastUsage.inputTokens, { ...attrs, [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_INPUT });
|
|
137
|
+
options.telemetry?.recordCounter('gen_ai.client.token.usage', lastUsage.outputTokens, { ...attrs, [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_OUTPUT });
|
|
138
|
+
}
|
|
139
|
+
if (lastFinishReason)
|
|
140
|
+
span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [lastFinishReason]);
|
|
141
|
+
options.telemetry?.recordHistogram('gen_ai.client.operation.duration', (Date.now() - started) / 1000, attrs);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async function* streamWithTelemetry(telemetry, name, attrs, iterate) {
|
|
145
|
+
const queue = [];
|
|
146
|
+
let done = false;
|
|
147
|
+
let failure;
|
|
148
|
+
let notify;
|
|
149
|
+
const wake = () => {
|
|
150
|
+
notify?.();
|
|
151
|
+
notify = undefined;
|
|
152
|
+
};
|
|
153
|
+
const producer = telemetry.span(name, attrs, async (span) => {
|
|
154
|
+
for await (const chunk of iterate(span)) {
|
|
155
|
+
queue.push(chunk);
|
|
156
|
+
wake();
|
|
157
|
+
}
|
|
158
|
+
}).catch((error) => {
|
|
159
|
+
failure = error;
|
|
160
|
+
}).finally(() => {
|
|
161
|
+
done = true;
|
|
162
|
+
wake();
|
|
163
|
+
});
|
|
164
|
+
while (!done || queue.length > 0) {
|
|
165
|
+
const next = queue.shift();
|
|
166
|
+
if (next !== undefined) {
|
|
167
|
+
yield next;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (failure)
|
|
171
|
+
throw failure;
|
|
172
|
+
await new Promise((resolve) => { notify = resolve; });
|
|
173
|
+
}
|
|
174
|
+
await producer;
|
|
175
|
+
if (failure)
|
|
176
|
+
throw failure;
|
|
177
|
+
}
|
|
178
|
+
async function withModelSpan(options, aliasKey, alias, method, ctx, fn) {
|
|
179
|
+
if (!options.telemetry)
|
|
180
|
+
return fn();
|
|
181
|
+
const started = Date.now();
|
|
182
|
+
const attrs = modelSpanAttrs(options, aliasKey, alias, method, ctx);
|
|
183
|
+
return options.telemetry.span(`chat ${alias.model}`, attrs, async (span) => {
|
|
184
|
+
const result = await fn();
|
|
185
|
+
const usage = result.usage;
|
|
186
|
+
const finishReason = result.finishReason;
|
|
187
|
+
if (usage) {
|
|
188
|
+
span.setAttributes({
|
|
189
|
+
[ATTR_GEN_AI_USAGE_INPUT_TOKENS]: usage.inputTokens,
|
|
190
|
+
[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: usage.outputTokens,
|
|
191
|
+
'gen_ai.usage.total_tokens': usage.totalTokens
|
|
192
|
+
});
|
|
193
|
+
options.telemetry?.recordCounter('gen_ai.client.token.usage', usage.inputTokens, { ...attrs, [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_INPUT });
|
|
194
|
+
options.telemetry?.recordCounter('gen_ai.client.token.usage', usage.outputTokens, { ...attrs, [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_OUTPUT });
|
|
195
|
+
}
|
|
196
|
+
if (finishReason)
|
|
197
|
+
span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [finishReason]);
|
|
198
|
+
options.telemetry?.recordHistogram('gen_ai.client.operation.duration', (Date.now() - started) / 1000, attrs);
|
|
199
|
+
return result;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function modelSpanAttrs(options, aliasKey, alias, method, ctx) {
|
|
203
|
+
return {
|
|
204
|
+
'harness.name': ctx?.harnessName ?? options.harnessName,
|
|
205
|
+
'harness.session.id': ctx?.sessionId,
|
|
206
|
+
'harness.run.id': ctx?.runId,
|
|
207
|
+
'harness.workflow.id': ctx?.workflowId,
|
|
208
|
+
'harness.agent.id': ctx?.agentId,
|
|
209
|
+
'harness.model.alias': aliasKey,
|
|
210
|
+
'harness.model.method': method,
|
|
211
|
+
[ATTR_GEN_AI_SYSTEM]: alias.provider.genAiSystem,
|
|
212
|
+
[ATTR_GEN_AI_REQUEST_MODEL]: alias.model,
|
|
213
|
+
'model.provider': alias.provider.id
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Validates alias capabilities for the requested operation.
|
|
218
|
+
*
|
|
219
|
+
* Throws {@link ModelCapabilityError} when required capabilities are missing.
|
|
220
|
+
*/
|
|
221
|
+
function ensureCapabilities(aliasKey, alias, method, req) {
|
|
222
|
+
if (!alias.capabilities.includes(method)) {
|
|
223
|
+
throw new ModelCapabilityError('Model alias does not provide requested capability.', {
|
|
224
|
+
alias: aliasKey,
|
|
225
|
+
method,
|
|
226
|
+
reason: 'missing_capability'
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (req.tools && req.tools.length > 0 && !alias.capabilities.includes('tool_use')) {
|
|
230
|
+
throw new ModelCapabilityError('Model alias does not support tool use.', {
|
|
231
|
+
alias: aliasKey,
|
|
232
|
+
method,
|
|
233
|
+
reason: 'missing_capability'
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const parts = (req.messages ?? []).flatMap((message) => Array.isArray(message.content) ? message.content : []);
|
|
237
|
+
const hasImageInput = parts.some((part) => part.kind === 'image' || part.kind === 'image_url');
|
|
238
|
+
if (hasImageInput && !alias.capabilities.includes('vision_input')) {
|
|
239
|
+
throw new ModelCapabilityError('Model alias does not support vision input.', {
|
|
240
|
+
alias: aliasKey,
|
|
241
|
+
method,
|
|
242
|
+
reason: 'missing_capability'
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const hasAudioInput = parts.some((part) => part.kind === 'audio');
|
|
246
|
+
if (hasAudioInput && !alias.capabilities.includes('audio_input')) {
|
|
247
|
+
throw new ModelCapabilityError('Model alias does not support audio input.', {
|
|
248
|
+
alias: aliasKey,
|
|
249
|
+
method,
|
|
250
|
+
reason: 'missing_capability'
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const hasFileInput = parts.some((part) => part.kind === 'file' || part.kind === 'file_url');
|
|
254
|
+
if (hasFileInput && !alias.capabilities.includes('file_input')) {
|
|
255
|
+
throw new ModelCapabilityError('Model alias does not support file input.', {
|
|
256
|
+
alias: aliasKey,
|
|
257
|
+
method,
|
|
258
|
+
reason: 'missing_capability'
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** Builds a standardized capability error when provider methods are missing. */
|
|
263
|
+
function methodMissing(alias, method) {
|
|
264
|
+
return new ModelCapabilityError('Model provider method is not implemented.', {
|
|
265
|
+
alias,
|
|
266
|
+
method,
|
|
267
|
+
reason: 'method_missing'
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/** Merges alias defaults with per-call overrides. */
|
|
271
|
+
function mergeDefaults(alias, call) {
|
|
272
|
+
const merged = {
|
|
273
|
+
...(alias.defaults ?? {}),
|
|
274
|
+
...(call ?? {}),
|
|
275
|
+
providerOptions: {
|
|
276
|
+
...(alias.defaults?.providerOptions ?? {}),
|
|
277
|
+
...(call?.providerOptions ?? {})
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const hasTopLevel = merged.temperature !== undefined
|
|
281
|
+
|| merged.maxTokens !== undefined
|
|
282
|
+
|| merged.topP !== undefined
|
|
283
|
+
|| merged.stopSequences !== undefined
|
|
284
|
+
|| Object.keys(merged.providerOptions ?? {}).length > 0;
|
|
285
|
+
return hasTopLevel ? merged : undefined;
|
|
286
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { JsonValue } from './json.js';
|
|
2
|
+
/** Session-level metadata persisted by a state store. */
|
|
3
|
+
export interface SessionRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
createdAt: string;
|
|
6
|
+
updatedAt: string;
|
|
7
|
+
runCount: number;
|
|
8
|
+
metadata?: Record<string, JsonValue>;
|
|
9
|
+
}
|
|
10
|
+
/** Message persisted in conversation history. */
|
|
11
|
+
export interface Message {
|
|
12
|
+
id: string;
|
|
13
|
+
sessionId: string;
|
|
14
|
+
runId?: string;
|
|
15
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
16
|
+
content: string;
|
|
17
|
+
toolCalls?: Array<{
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
arguments: JsonValue;
|
|
21
|
+
}>;
|
|
22
|
+
toolResults?: Array<{
|
|
23
|
+
toolCallId: string;
|
|
24
|
+
output?: JsonValue;
|
|
25
|
+
error?: SerializedError;
|
|
26
|
+
}>;
|
|
27
|
+
timestamp: string;
|
|
28
|
+
}
|
|
29
|
+
/** Run lifecycle status values.
|
|
30
|
+
* - `running`: active run in progress
|
|
31
|
+
* - `succeeded`: run completed successfully
|
|
32
|
+
* - `failed`: run completed with error
|
|
33
|
+
* - `cancelled`: run cancelled before completion
|
|
34
|
+
*/
|
|
35
|
+
export type RunStatus = 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
36
|
+
/** Serialized error payload stored on run records. */
|
|
37
|
+
export interface SerializedError {
|
|
38
|
+
code: string;
|
|
39
|
+
message: string;
|
|
40
|
+
category?: string;
|
|
41
|
+
retriable?: boolean;
|
|
42
|
+
meta?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
/** Run record persisted by state stores. */
|
|
45
|
+
export interface RunRecord {
|
|
46
|
+
id: string;
|
|
47
|
+
sessionId: string;
|
|
48
|
+
kind: 'workflow' | 'agent';
|
|
49
|
+
target: string;
|
|
50
|
+
startedAt: string;
|
|
51
|
+
finishedAt?: string;
|
|
52
|
+
status: RunStatus;
|
|
53
|
+
input?: JsonValue;
|
|
54
|
+
output?: JsonValue;
|
|
55
|
+
error?: SerializedError;
|
|
56
|
+
}
|
|
57
|
+
/** Event payload persisted for run replay or audit. */
|
|
58
|
+
export interface PersistedRunEvent {
|
|
59
|
+
id: string;
|
|
60
|
+
runId: string;
|
|
61
|
+
at: string;
|
|
62
|
+
type: string;
|
|
63
|
+
payload: JsonValue;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { HarnessError } from '../errors/index.js';
|
|
2
|
+
import type { Logger } from '../logger/index.js';
|
|
3
|
+
import type { EmbeddingRequest, EmbeddingResponse, ModelProvider, ObjectRequest, ObjectResponse, ObjectStreamChunk, RerankRequest, RerankResponse, TextRequest, TextResponse, TextStreamChunk } from './model-provider.js';
|
|
4
|
+
import type { JsonValue } from '../models/json.js';
|
|
5
|
+
import type { TelemetryShim } from '../telemetry/index.js';
|
|
6
|
+
import type { HarnessAdapterContext } from './harness-context.js';
|
|
7
|
+
export interface BaseModelProviderOptions {
|
|
8
|
+
id: string;
|
|
9
|
+
genAiSystem: string;
|
|
10
|
+
logger?: Logger;
|
|
11
|
+
telemetry?: TelemetryShim;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
type ProviderMethod = 'text' | 'textStream' | 'object' | 'objectStream' | 'embed' | 'rerank';
|
|
15
|
+
type ProviderRequest = TextRequest | ObjectRequest | EmbeddingRequest | RerankRequest;
|
|
16
|
+
/**
|
|
17
|
+
* Base class for model adapters.
|
|
18
|
+
*
|
|
19
|
+
* Adapter packages should map provider-specific requests/responses in protected
|
|
20
|
+
* `do*` methods. The base class owns cross-cutting harness behavior:
|
|
21
|
+
* cancellation, timeout, safe logs, metrics/spans, and error normalization.
|
|
22
|
+
*/
|
|
23
|
+
export declare abstract class BaseModelProvider implements ModelProvider {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly genAiSystem: string;
|
|
26
|
+
private logger;
|
|
27
|
+
private telemetry;
|
|
28
|
+
private timeoutMs;
|
|
29
|
+
protected constructor(options: BaseModelProviderOptions);
|
|
30
|
+
/**
|
|
31
|
+
* Called by the harness during composition so adapters automatically inherit
|
|
32
|
+
* harness-level logging, telemetry, and timeout defaults. Explicit adapter
|
|
33
|
+
* constructor options win over inherited values.
|
|
34
|
+
*/
|
|
35
|
+
configureHarnessContext(context: HarnessAdapterContext): void;
|
|
36
|
+
text(req: TextRequest): Promise<TextResponse>;
|
|
37
|
+
textStream(req: TextRequest): AsyncIterable<TextStreamChunk>;
|
|
38
|
+
object<T extends JsonValue = JsonValue>(req: ObjectRequest<T>): Promise<ObjectResponse<T>>;
|
|
39
|
+
objectStream<T extends JsonValue = JsonValue>(req: ObjectRequest<T>): AsyncIterable<ObjectStreamChunk<T>>;
|
|
40
|
+
embed(req: EmbeddingRequest): Promise<EmbeddingResponse>;
|
|
41
|
+
rerank(req: RerankRequest): Promise<RerankResponse>;
|
|
42
|
+
protected doText(_req: TextRequest): Promise<TextResponse>;
|
|
43
|
+
protected doTextStream(_req: TextRequest): AsyncIterable<TextStreamChunk>;
|
|
44
|
+
protected doObject<T extends JsonValue = JsonValue>(_req: ObjectRequest<T>): Promise<ObjectResponse<T>>;
|
|
45
|
+
protected doObjectStream<T extends JsonValue = JsonValue>(_req: ObjectRequest<T>): AsyncIterable<ObjectStreamChunk<T>>;
|
|
46
|
+
protected doEmbed(_req: EmbeddingRequest): Promise<EmbeddingResponse>;
|
|
47
|
+
protected doRerank(_req: RerankRequest): Promise<RerankResponse>;
|
|
48
|
+
protected normalizeError(error: unknown, method: ProviderMethod, req: ProviderRequest): HarnessError;
|
|
49
|
+
private call;
|
|
50
|
+
private stream;
|
|
51
|
+
private withTimeout;
|
|
52
|
+
private methodMissing;
|
|
53
|
+
private attrs;
|
|
54
|
+
private recordUsage;
|
|
55
|
+
}
|
|
56
|
+
export {};
|