@noetaris/harness-types 0.8.0 → 0.10.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 +122 -9
- package/dist/index.d.ts +93 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,20 +13,21 @@ This package defines the common TypeScript interfaces that all LLM adapter packa
|
|
|
13
13
|
| Type | Description |
|
|
14
14
|
|------|-------------|
|
|
15
15
|
| `Message` | Discriminated union: `{ role: 'user'; content: string \| ContentBlock[] }` \| `{ role: 'assistant'; content?: string; toolCalls?: ToolCall[] }` \| `{ role: 'tool'; toolCallId: string; content: string }` |
|
|
16
|
-
| `TextBlock` | `{ type: 'text'; text: string }` — a run of text inside a user message |
|
|
17
|
-
| `ImageBlock` | `{ type: 'image'; data: string; mediaType: string }` — an image inside a user message; `data` is raw base64 (see [Image input](#image-input)) |
|
|
16
|
+
| `TextBlock` | `{ type: 'text'; text: string; cachePoint?: true }` — a run of text inside a user message or a system prompt (`cachePoint`: see [Prompt caching](#prompt-caching)) |
|
|
17
|
+
| `ImageBlock` | `{ type: 'image'; data: string; mediaType: string; cachePoint?: true }` — an image inside a user message; `data` is raw base64 (see [Image input](#image-input)) |
|
|
18
18
|
| `ContentBlock` | `TextBlock \| ImageBlock` |
|
|
19
19
|
| `Tool` | `{ name: string; description: string; inputSchema: Record<string, unknown> }` |
|
|
20
20
|
| `ToolCall` | `{ id: string; name: string; input: unknown }` |
|
|
21
|
-
| `LLMResponse` | `{ text: string; toolCalls: ToolCall[]; stopReason: 'end' \| 'tool_use' \| 'max_tokens'; usage: { inputTokens: number; outputTokens: number; contextWindowSize?: number } }` — `usage` is always present; all adapters must populate it |
|
|
22
|
-
| `LLMRequestEvent` | `"llm.request"` payload: `{ callId: string; modelId: string; providerName: string; messages?: Message[]; tools?: Tool[] }` (`messages`/`tools` opt-in, the harness shapes passed to `invoke()`; `callId` pairs the event with its `"llm.response"` or `"llm.error"`) |
|
|
23
|
-
| `LLMUsageEvent` | `"llm.response"` payload: `{ callId: string; tokens: { input: number; output: number }; modelId: string; stopReason: LLMResponse['stopReason']; providerName: string; output?: LLMResponse; contextWindowSize?: number }` (`output` opt-in) |
|
|
24
|
-
| `LLMErrorEvent` | `"llm.error"` payload: `{ callId: string; modelId: string; providerName: string; error: unknown }` — emitted when the call throws after `"llm.request"`; `error` is the thrown value, which the adapter rethrows |
|
|
21
|
+
| `LLMResponse` | `{ text: string; toolCalls: ToolCall[]; stopReason: 'end' \| 'tool_use' \| 'max_tokens'; usage: { inputTokens: number; outputTokens: number; cacheReadTokens?: number; cacheWriteTokens?: number; cacheWriteTokensByTtl?: Record<string, number>; contextWindowSize?: number } }` — `usage` is always present; all adapters must populate it. `inputTokens` is the total, cached tokens included (see [Prompt caching](#prompt-caching)) |
|
|
22
|
+
| `LLMRequestEvent` | `"llm.request"` payload: `{ callId: string; modelId: string; providerName: string; messages?: Message[]; tools?: Tool[]; system?: string \| TextBlock[] }` (`messages`/`tools`/`system` opt-in, the harness shapes passed to `invoke()`; `callId` pairs the event with its `"llm.response"` or `"llm.error"`) |
|
|
23
|
+
| `LLMUsageEvent` | `"llm.response"` payload: `{ callId: string; tokens: { input: number; output: number; cacheRead?: number; cacheWrite?: number; cacheWriteByTtl?: Record<string, number> }; modelId: string; stopReason: LLMResponse['stopReason']; providerName: string; output?: LLMResponse; contextWindowSize?: number }` (`output` opt-in; `tokens` carries the same numbers as `usage`, mock adapters included) |
|
|
24
|
+
| `LLMErrorEvent` | `"llm.error"` payload: `{ callId: string; modelId: string; providerName: string; error: unknown }` — emitted when the call throws after `"llm.request"`; `error` is the thrown value, which the adapter rethrows (`signal.reason` for a cancelled call, see [Cancelling a call](#cancelling-a-call)) |
|
|
25
25
|
| `ToolCallEvent` | `"tool.call"` payload: `{ toolName: string; toolCallId: string; input?: unknown }` (`input` opt-in) |
|
|
26
26
|
| `ToolResultEvent` | `"tool.result"` payload: `{ toolName: string; toolCallId: string; durationMs: number; result?: unknown; error?: unknown }` (`result` opt-in; `error` is set whenever the tool threw) |
|
|
27
27
|
| `ToolRunContext` | `{ emit(name, payload?): void; within<T>(type, payload, fn): Promise<T> }` — the part of a step `ctx` that `runTool` needs |
|
|
28
28
|
| `RunToolOptions` | `{ captureIO?: boolean }` — options of `runTool` |
|
|
29
|
-
| `
|
|
29
|
+
| `InvokeOptions` | `{ tools?: Tool[]; signal?: AbortSignal; system?: string \| TextBlock[] }` — options of `invoke()` |
|
|
30
|
+
| `LLM` | `{ invoke(messages: Message[], options?: InvokeOptions): Promise<LLMResponse> }` |
|
|
30
31
|
|
|
31
32
|
The runtime exports are `toBlocks(content: string | ContentBlock[]): ContentBlock[]`, which turns a string into a single text block and returns an array as it is, and [`runTool`](#reporting-tool-calls).
|
|
32
33
|
|
|
@@ -52,8 +53,108 @@ const message: Message = {
|
|
|
52
53
|
- `content: []` is allowed by the type, but providers may reject it. Don't send an empty array.
|
|
53
54
|
- `mediaType` is a plain string. Anthropic, OpenAI and Ollama check it and throw on types they do not accept. Gemini passes any value through and the provider decides. The accepted types differ per adapter. See the README of your adapter: [Anthropic](https://github.com/noetaris-lab/harness-anthropic), [OpenAI](https://github.com/noetaris-lab/harness-openai), [Gemini](https://github.com/noetaris-lab/harness-google), [Ollama](https://github.com/noetaris-lab/harness-ollama).
|
|
54
55
|
- The model must be able to read images. The adapters do not check this.
|
|
56
|
+
- An image block can carry `cachePoint: true`, like a text block. See [Prompt caching](#prompt-caching).
|
|
55
57
|
- Large images in session state are expensive to store. See [Large media in session state](https://github.com/noetaris-lab/harness/blob/main/LIMITATIONS.md).
|
|
56
58
|
|
|
59
|
+
## Cancelling a call
|
|
60
|
+
|
|
61
|
+
Pass an `AbortSignal` as `signal` to stop waiting for a call:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const response = await llm.invoke(messages, { signal: AbortSignal.timeout(30_000) })
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- When the signal aborts, the adapter closes the HTTP request and `invoke()` rejects with `signal.reason`, not the provider SDK's own error. That is an `AbortError` `DOMException` for `controller.abort()` and a `TimeoutError` for `AbortSignal.timeout()`.
|
|
68
|
+
- Already aborted when `invoke()` is called: it rejects with no observer event.
|
|
69
|
+
- Aborted after `"llm.request"`: `"llm.error"` is emitted with `error: signal.reason`, then `invoke()` rejects with the same value.
|
|
70
|
+
- Aborting frees the caller and closes the connection. It does **not** guarantee that the provider stops the work or stops billing for it.
|
|
71
|
+
|
|
72
|
+
To tell a cancel from a failure, the caller checks `signal.aborted` (or `error === signal.reason`). An observer of `"llm.error"` does not see the signal. It can use `error.name` only for the default reasons: `AbortError` from `controller.abort()` with no argument, `TimeoutError` from `AbortSignal.timeout()`. A custom reason (`controller.abort(new Error('x'))`, `controller.abort('x')`) keeps its own name or has none.
|
|
73
|
+
|
|
74
|
+
**Inside a step**, the harness does not pass the run's signal for you. To let `run.stop()` cut the call, pass it yourself:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const response = await ctx.llm.invoke(state.messages, { signal: ctx.signal })
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Why it is not automatic: without it, `run.stop()` waits for the step to finish and pauses at the step boundary. With it, the step's call rejects, so the step fails with `$error` and may take its `onError` route. Each step chooses which behaviour it wants.
|
|
81
|
+
|
|
82
|
+
## Prompt caching
|
|
83
|
+
|
|
84
|
+
Some providers can reuse the start of a prompt they have seen before (a *prefix*) and bill it at a lower price.
|
|
85
|
+
|
|
86
|
+
**Cache points.** Mark the end of a cacheable prefix with `cachePoint: true` on a text or image block:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const messages: Message[] = [{
|
|
90
|
+
role: 'user',
|
|
91
|
+
content: [
|
|
92
|
+
{ type: 'text', text: longDocument, cachePoint: true },
|
|
93
|
+
{ type: 'text', text: 'Summarise the document.' },
|
|
94
|
+
],
|
|
95
|
+
}]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- The provider may cache everything up to and including the marked block: the system prompt first, then the messages in order.
|
|
99
|
+
- Adapters whose provider caches only at marked points (Anthropic) send it. Adapters whose provider caches automatically (OpenAI, Gemini) ignore it, and so does Ollama.
|
|
100
|
+
- It is a hint, not a guarantee. The provider decides whether a cache entry is written or read (for example, a prefix may be too short to cache).
|
|
101
|
+
- Anthropic allows at most 4 points per request. Its adapter throws before `"llm.request"` when there are more. With the adapter's `autoCache` option on, the automatic marker takes one of the 4.
|
|
102
|
+
- `cachePoint` takes only `true`. Omit the field for no point.
|
|
103
|
+
- Tool messages (`role: 'tool'`) cannot be marked.
|
|
104
|
+
|
|
105
|
+
**Usage fields.** `LLMResponse.usage` (and the `"llm.response"` event's `tokens`) report:
|
|
106
|
+
|
|
107
|
+
| Field | Event field | Meaning |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `inputTokens` | `input` | Total input tokens of the call, **cached tokens included**. Compare it with `contextWindowSize`. |
|
|
110
|
+
| `cacheReadTokens?` | `cacheRead?` | Input tokens read from the cache. A part of `inputTokens`, not added to it. |
|
|
111
|
+
| `cacheWriteTokens?` | `cacheWrite?` | Input tokens written to the cache. A part of `inputTokens`, not added to it. |
|
|
112
|
+
| `cacheWriteTokensByTtl?` | `cacheWriteByTtl?` | `cacheWriteTokens` split by TTL. Keys are the provider's TTL labels; values add up to `cacheWriteTokens`. |
|
|
113
|
+
|
|
114
|
+
An absent cache field means the adapter cannot know. A present `0` is a real count (no cache hit, nothing written).
|
|
115
|
+
|
|
116
|
+
The uncached part, billed at the normal input price, is:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
uncached = inputTokens − (cacheReadTokens ?? 0) − (cacheWriteTokens ?? 0)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Writes by TTL.** A cache write's price depends on how long the entry lives. On Anthropic, a 5-minute write costs 1.25× the input price and a 1-hour write 2×. `cacheWriteTokensByTtl` splits `cacheWriteTokens` by TTL so each part can be priced:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// usage: { inputTokens: 2000, outputTokens: 50, cacheReadTokens: 0,
|
|
126
|
+
// cacheWriteTokens: 1500, cacheWriteTokensByTtl: { '5m': 500, '1h': 1000 } }
|
|
127
|
+
const writeCost = Object.entries(usage.cacheWriteTokensByTtl ?? {})
|
|
128
|
+
.reduce((sum, [ttl, tokens]) => sum + tokens * writePrice(ttl), 0)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- Keys are the provider's TTL labels (`'5m'`, `'1h'`, `'30m'`, ...). They are plain strings because the labels differ by provider.
|
|
132
|
+
- When present, the values add up to `cacheWriteTokens`.
|
|
133
|
+
- Absent means the adapter cannot tell the split, not that nothing was written. Each adapter's README lists the keys it reports; a reported key with `0` is a real count.
|
|
134
|
+
|
|
135
|
+
Which fields each adapter reports is in its README.
|
|
136
|
+
|
|
137
|
+
## System prompt
|
|
138
|
+
|
|
139
|
+
Pass the system prompt as the `system` option, a string or text blocks:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
await llm.invoke(messages, { system: 'You are a careful reviewer. Answer in English.' })
|
|
143
|
+
|
|
144
|
+
await llm.invoke(messages, {
|
|
145
|
+
system: [
|
|
146
|
+
{ type: 'text', text: styleGuide, cachePoint: true },
|
|
147
|
+
{ type: 'text', text: 'Review the diff below.' },
|
|
148
|
+
],
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- Each adapter sends it in its provider's native system slot. See your adapter's README for the mapping.
|
|
153
|
+
- Text only: image blocks are not accepted.
|
|
154
|
+
- A block of the system prompt can carry `cachePoint`. The system prompt is the most stable prefix, so it is a good place for one.
|
|
155
|
+
- It is an option, not a `Message` role. It is not stored in the conversation, so pass it on every call. A session resumed later uses the prompt your code passes then, not an old one saved in state. There is exactly one system prompt per call.
|
|
156
|
+
- On `"llm.request"`, `system` is opt-in like `messages`: adapters include it only when input capture is on.
|
|
157
|
+
|
|
57
158
|
## Reporting tool calls
|
|
58
159
|
|
|
59
160
|
`runTool(ctx, call, fn, options?)` runs one tool call and reports it. It emits `"tool.call"`,
|
|
@@ -93,15 +194,26 @@ Requires Node.js ≥ 22.
|
|
|
93
194
|
## Usage
|
|
94
195
|
|
|
95
196
|
```ts
|
|
96
|
-
import type { LLM, Message, LLMResponse,
|
|
197
|
+
import type { LLM, Message, LLMResponse, InvokeOptions } from '@noetaris/harness-types'
|
|
97
198
|
|
|
98
199
|
class MyAdapter implements LLM {
|
|
99
|
-
async invoke(messages: Message[], options?:
|
|
200
|
+
async invoke(messages: Message[], options?: InvokeOptions): Promise<LLMResponse> {
|
|
201
|
+
// options.tools, options.signal, options.system
|
|
100
202
|
// ...
|
|
101
203
|
}
|
|
102
204
|
}
|
|
103
205
|
```
|
|
104
206
|
|
|
207
|
+
A caller, inside a step:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
const response = await ctx.llm.invoke(state.messages, {
|
|
211
|
+
system: 'You are a helpful assistant.',
|
|
212
|
+
tools,
|
|
213
|
+
signal: ctx.signal,
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
105
217
|
An adapter that handles image input can use `toBlocks()` to treat a string and an array the same way:
|
|
106
218
|
|
|
107
219
|
```ts
|
|
@@ -124,6 +236,7 @@ for (const block of toBlocks(message.content)) {
|
|
|
124
236
|
- [`@noetaris/harness-anthropic`](https://github.com/noetaris-lab/harness-anthropic) — Anthropic Claude adapter
|
|
125
237
|
- [`@noetaris/harness-openai`](https://github.com/noetaris-lab/harness-openai) — OpenAI adapter
|
|
126
238
|
- [`@noetaris/harness-google`](https://github.com/noetaris-lab/harness-google) — Google Gemini adapter
|
|
239
|
+
- [`@noetaris/harness-ollama`](https://github.com/noetaris-lab/harness-ollama) — Ollama adapter
|
|
127
240
|
|
|
128
241
|
## License
|
|
129
242
|
|
package/dist/index.d.ts
CHANGED
|
@@ -33,10 +33,20 @@ declare function runTool<T>(ctx: ToolRunContext, call: Pick<ToolCall, 'id' | 'na
|
|
|
33
33
|
*
|
|
34
34
|
* @packageDocumentation
|
|
35
35
|
*/
|
|
36
|
-
/** A run of plain text inside a user message. */
|
|
36
|
+
/** A run of plain text inside a user message or a system prompt. */
|
|
37
37
|
type TextBlock = {
|
|
38
38
|
type: 'text';
|
|
39
39
|
text: string;
|
|
40
|
+
/**
|
|
41
|
+
* Marks the end of a cacheable prefix: the provider may cache everything up to and including
|
|
42
|
+
* this block (the system prompt first, then the messages in order). Adapters whose provider
|
|
43
|
+
* caches only at marked points (Anthropic) send it; adapters whose provider caches
|
|
44
|
+
* automatically ignore it. A hint, not a guarantee: the provider decides whether a cache
|
|
45
|
+
* entry is written or read (e.g. a minimum prefix length). Anthropic allows at most 4 points
|
|
46
|
+
* per request (its `autoCache` option takes one of them); its adapter throws before
|
|
47
|
+
* `"llm.request"` when there are more. See the adapter's README.
|
|
48
|
+
*/
|
|
49
|
+
cachePoint?: true;
|
|
40
50
|
};
|
|
41
51
|
/**
|
|
42
52
|
* An image inside a user message.
|
|
@@ -48,6 +58,16 @@ type ImageBlock = {
|
|
|
48
58
|
type: 'image';
|
|
49
59
|
data: string;
|
|
50
60
|
mediaType: string;
|
|
61
|
+
/**
|
|
62
|
+
* Marks the end of a cacheable prefix: the provider may cache everything up to and including
|
|
63
|
+
* this block (the system prompt first, then the messages in order). Adapters whose provider
|
|
64
|
+
* caches only at marked points (Anthropic) send it; adapters whose provider caches
|
|
65
|
+
* automatically ignore it. A hint, not a guarantee: the provider decides whether a cache
|
|
66
|
+
* entry is written or read (e.g. a minimum prefix length). Anthropic allows at most 4 points
|
|
67
|
+
* per request (its `autoCache` option takes one of them); its adapter throws before
|
|
68
|
+
* `"llm.request"` when there are more. See the adapter's README.
|
|
69
|
+
*/
|
|
70
|
+
cachePoint?: true;
|
|
51
71
|
};
|
|
52
72
|
/** One piece of user-message content. */
|
|
53
73
|
type ContentBlock = TextBlock | ImageBlock;
|
|
@@ -112,10 +132,32 @@ interface LLMResponse {
|
|
|
112
132
|
toolCalls: ToolCall[];
|
|
113
133
|
stopReason: 'end' | 'tool_use' | 'max_tokens';
|
|
114
134
|
usage: {
|
|
115
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Total input tokens of this call, cached tokens included. Compare it with
|
|
137
|
+
* `contextWindowSize`. The uncached part, billed at the normal input price, is
|
|
138
|
+
* `inputTokens − (cacheReadTokens ?? 0) − (cacheWriteTokens ?? 0)`.
|
|
139
|
+
*/
|
|
116
140
|
inputTokens: number;
|
|
117
141
|
/** Number of output tokens generated by this call. */
|
|
118
142
|
outputTokens: number;
|
|
143
|
+
/**
|
|
144
|
+
* Input tokens read from the provider's prompt cache. Part of `inputTokens`, not added to
|
|
145
|
+
* it. Absent when the adapter cannot know; `0` is a real count (no cache hit).
|
|
146
|
+
*/
|
|
147
|
+
cacheReadTokens?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Input tokens written to the provider's prompt cache. Part of `inputTokens`, not added to
|
|
150
|
+
* it. Absent when the adapter cannot know; `0` is a real count.
|
|
151
|
+
*/
|
|
152
|
+
cacheWriteTokens?: number;
|
|
153
|
+
/**
|
|
154
|
+
* `cacheWriteTokens` split by cache lifetime (TTL). Each key is the provider's TTL label
|
|
155
|
+
* (for example `'5m'`, `'1h'`, `'30m'`); each value is the input tokens written with that
|
|
156
|
+
* TTL. The values add up to `cacheWriteTokens`, which is then present too. Absent when the
|
|
157
|
+
* adapter cannot tell the split. Which keys an adapter reports is in its README; a reported
|
|
158
|
+
* key with `0` is a real count. Keys are strings because TTL labels differ by provider.
|
|
159
|
+
*/
|
|
160
|
+
cacheWriteTokensByTtl?: Record<string, number>;
|
|
119
161
|
/**
|
|
120
162
|
* The model's total context window capacity in tokens.
|
|
121
163
|
* Present only when the adapter can determine it.
|
|
@@ -132,7 +174,7 @@ interface LLMResponse {
|
|
|
132
174
|
* `"llm.response"` ({@link LLMUsageEvent}) or `"llm.error"` ({@link LLMErrorEvent}),
|
|
133
175
|
* carrying the same `callId`. A throw before this event emits nothing.
|
|
134
176
|
*
|
|
135
|
-
* `messages` and `
|
|
177
|
+
* `messages`, `tools` and `system` are opt-in — they may contain sensitive data. When present they
|
|
136
178
|
* are the normalized harness shapes passed to `invoke()`, not the provider-translated
|
|
137
179
|
* payloads. They are passed by reference; observers must not mutate them.
|
|
138
180
|
*/
|
|
@@ -148,12 +190,17 @@ interface LLMRequestEvent {
|
|
|
148
190
|
providerName: string;
|
|
149
191
|
messages?: Message[];
|
|
150
192
|
tools?: Tool[];
|
|
193
|
+
system?: string | TextBlock[];
|
|
151
194
|
}
|
|
152
195
|
/**
|
|
153
196
|
* Token-usage payload emitted by LLM adapters as an `"llm.response"` observer event,
|
|
154
197
|
* after a successful provider call.
|
|
155
198
|
*
|
|
156
|
-
*
|
|
199
|
+
* `tokens` carries the same numbers as the returned `LLMResponse.usage`: `input` = `inputTokens`,
|
|
200
|
+
* `output` = `outputTokens`, `cacheRead` = `cacheReadTokens`, `cacheWrite` = `cacheWriteTokens`,
|
|
201
|
+
* `cacheWriteByTtl` = `cacheWriteTokensByTtl` (present exactly when the usage field is). Mock
|
|
202
|
+
* adapters report the `usage` of the response they return. `cacheWriteByTtl` may be the same
|
|
203
|
+
* object as in `usage`; observers must not mutate it.
|
|
157
204
|
*
|
|
158
205
|
* `output` is opt-in — it may contain sensitive data; adapters default to omitting it. When
|
|
159
206
|
* present it is the normalized {@link LLMResponse} that `invoke()` returns, passed by reference.
|
|
@@ -166,6 +213,9 @@ interface LLMUsageEvent {
|
|
|
166
213
|
tokens: {
|
|
167
214
|
input: number;
|
|
168
215
|
output: number;
|
|
216
|
+
cacheRead?: number;
|
|
217
|
+
cacheWrite?: number;
|
|
218
|
+
cacheWriteByTtl?: Record<string, number>;
|
|
169
219
|
};
|
|
170
220
|
modelId: string;
|
|
171
221
|
stopReason: LLMResponse['stopReason'];
|
|
@@ -177,6 +227,13 @@ interface LLMUsageEvent {
|
|
|
177
227
|
* Payload emitted by LLM adapters as an `"llm.error"` observer event, when anything throws
|
|
178
228
|
* after `"llm.request"` was emitted and before `"llm.response"` would be (the provider call,
|
|
179
229
|
* response normalization). The adapter then rethrows the same value.
|
|
230
|
+
*
|
|
231
|
+
* When the call's `signal` aborted, `error` is `signal.reason`, unchanged. The caller can tell a
|
|
232
|
+
* cancel from a failure reliably with `signal.aborted` (or `error === signal.reason`). An
|
|
233
|
+
* observer, which does not see the signal, can use `error.name` only for the default reasons:
|
|
234
|
+
* `AbortError` from `controller.abort()` with no argument, `TimeoutError` from
|
|
235
|
+
* `AbortSignal.timeout()`; a custom reason (`controller.abort(new Error('x'))`,
|
|
236
|
+
* `controller.abort('x')`) keeps its own name or has none.
|
|
180
237
|
*/
|
|
181
238
|
interface LLMErrorEvent {
|
|
182
239
|
/** Correlation id matching the `"llm.request"` event for the same `invoke()` call (see {@link LLMRequestEvent.callId}). */
|
|
@@ -210,12 +267,37 @@ interface ToolResultEvent {
|
|
|
210
267
|
result?: unknown;
|
|
211
268
|
error?: unknown;
|
|
212
269
|
}
|
|
270
|
+
/** Options of {@link LLM.invoke}. */
|
|
271
|
+
interface InvokeOptions {
|
|
272
|
+
/** Tools the model may call. */
|
|
273
|
+
tools?: Tool[];
|
|
274
|
+
/**
|
|
275
|
+
* Cancels the call. When the signal aborts, the adapter closes the HTTP request and `invoke()`
|
|
276
|
+
* rejects with `signal.reason` (an `AbortError` `DOMException` for `controller.abort()`, a
|
|
277
|
+
* `TimeoutError` for `AbortSignal.timeout()`), not the provider SDK's own error. If the signal
|
|
278
|
+
* is already aborted when `invoke()` is called, it rejects with no observer event. If it aborts
|
|
279
|
+
* after `"llm.request"`, `"llm.error"` is emitted with `error: signal.reason` first.
|
|
280
|
+
*
|
|
281
|
+
* Aborting frees the caller and closes the connection. It does not guarantee that the provider
|
|
282
|
+
* stops the work or stops billing for it.
|
|
283
|
+
*
|
|
284
|
+
* The harness does not pass the run's signal for you. To let `run.stop()` cut the call, pass it
|
|
285
|
+
* from the step: `ctx.llm.invoke(messages, { signal: ctx.signal })`.
|
|
286
|
+
*/
|
|
287
|
+
signal?: AbortSignal;
|
|
288
|
+
/**
|
|
289
|
+
* The system prompt for this call. Each adapter sends it in its provider's native system slot.
|
|
290
|
+
* A string, or text blocks (a block may carry `cachePoint`). Not stored in the conversation:
|
|
291
|
+
* pass it on every call.
|
|
292
|
+
*/
|
|
293
|
+
system?: string | TextBlock[];
|
|
294
|
+
}
|
|
213
295
|
/**
|
|
214
296
|
* Core interface every LLM adapter must implement.
|
|
215
297
|
*
|
|
216
298
|
* @example
|
|
217
299
|
* ```ts
|
|
218
|
-
* const response = await llm.invoke(messages, { tools: [myTool] })
|
|
300
|
+
* const response = await llm.invoke(messages, { tools: [myTool], system: 'Be brief.', signal: ctx.signal })
|
|
219
301
|
* ```
|
|
220
302
|
*/
|
|
221
303
|
interface LLM {
|
|
@@ -223,11 +305,12 @@ interface LLM {
|
|
|
223
305
|
* Send a message list to the LLM and return a normalized response.
|
|
224
306
|
*
|
|
225
307
|
* @param messages - Conversation history, oldest first.
|
|
226
|
-
* @param options
|
|
308
|
+
* @param options - See {@link InvokeOptions}.
|
|
309
|
+
* @param options.tools - Tools the model may call.
|
|
310
|
+
* @param options.signal - Cancels the call; `invoke()` then rejects with `signal.reason`.
|
|
311
|
+
* @param options.system - The system prompt, sent in the provider's native system slot.
|
|
227
312
|
*/
|
|
228
|
-
invoke(messages: Message[], options?:
|
|
229
|
-
tools?: Tool[];
|
|
230
|
-
}): Promise<LLMResponse>;
|
|
313
|
+
invoke(messages: Message[], options?: InvokeOptions): Promise<LLMResponse>;
|
|
231
314
|
}
|
|
232
315
|
|
|
233
|
-
export { type ContentBlock, type ImageBlock, type LLM, type LLMErrorEvent, type LLMRequestEvent, type LLMResponse, type LLMUsageEvent, type Message, type RunToolOptions, type TextBlock, type Tool, type ToolCall, type ToolCallEvent, type ToolResultEvent, type ToolRunContext, runTool, toBlocks };
|
|
316
|
+
export { type ContentBlock, type ImageBlock, type InvokeOptions, type LLM, type LLMErrorEvent, type LLMRequestEvent, type LLMResponse, type LLMUsageEvent, type Message, type RunToolOptions, type TextBlock, type Tool, type ToolCall, type ToolCallEvent, type ToolResultEvent, type ToolRunContext, runTool, toBlocks };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/run-tool.ts","../src/index.ts"],"sourcesContent":["import type { ToolCall, ToolCallEvent, ToolResultEvent } from './index.js'\n\n/**\n * The part of a step ctx {@link runTool} needs. The `@noetaris/harness` step ctx (0.7 and\n * later) satisfies it structurally.\n */\nexport interface ToolRunContext {\n emit(name: string, payload?: unknown): void\n within<T>(type: string, payload: unknown, fn: () => Promise<T>): Promise<T>\n}\n\nexport interface RunToolOptions {\n /**\n * Put the tool's `input` on `\"tool.call\"` and its `result` on `\"tool.result\"`. They may\n * contain sensitive data. Default: `false`.\n */\n captureIO?: boolean\n}\n\n/**\n * Run one tool call and report it: emits `\"tool.call\"`, runs `fn` inside the observer's scope\n * for that event (`ctx.within('tool.call', { toolCallId })`), then emits `\"tool.result\"` with\n * `durationMs` — and `error` when `fn` threw, in which case the same value is rethrown.\n * Returns `fn`'s value.\n *\n * @example\n * ```ts\n * const outputs = await Promise.all(\n * res.toolCalls.map(call => runTool(ctx, call, () => toolImpls[call.name](call.input))),\n * )\n * ```\n */\nexport async function runTool<T>(\n ctx: ToolRunContext,\n call: Pick<ToolCall, 'id' | 'name' | 'input'>,\n fn: () => Promise<T>,\n options?: RunToolOptions,\n): Promise<T> {\n const captureIO = options?.captureIO === true\n const base = { toolName: call.name, toolCallId: call.id }\n const callEvent: ToolCallEvent = captureIO ? { ...base, input: call.input } : base\n ctx.emit('tool.call', callEvent)\n\n const start = Date.now()\n let value: T\n try {\n value = await ctx.within('tool.call', { toolCallId: call.id }, fn)\n } catch (error) {\n const failed: ToolResultEvent = {\n ...base,\n durationMs: Date.now() - start,\n // Consumers read `error !== undefined` as \"the tool failed\", so a thrown undefined is named.\n error: error === undefined ? new Error('tool threw undefined') : error,\n }\n ctx.emit('tool.result', failed)\n throw error\n }\n const succeeded: ToolResultEvent = captureIO\n ? { ...base, durationMs: Date.now() - start, result: value }\n : { ...base, durationMs: Date.now() - start }\n ctx.emit('tool.result', succeeded)\n return value\n}\n","/**\n * Shared LLM protocol types used by all harness LLM adapters.\n *\n * @packageDocumentation\n */\n\n/** A run of plain text inside a user message. */\nexport type TextBlock = { type: 'text'; text: string }\n\n/**\n * An image inside a user message.\n *\n * `data` is raw base64 with no `data:` prefix. `mediaType` is the MIME type\n * (e.g. `'image/png'`); Anthropic, OpenAI and Ollama throw on types they do not accept; Gemini passes any value through.\n */\nexport type ImageBlock = { type: 'image'; data: string; mediaType: string }\n\n/** One piece of user-message content. */\nexport type ContentBlock = TextBlock | ImageBlock\n\n/**\n * A single turn in a conversation.\n *\n * - `user` — input from the human side: a string, or blocks mixing text and images.\n * An empty block array is allowed by the type but may be rejected by providers; don't send one.\n * - `assistant` — model reply, optionally carrying tool-call requests.\n * - `tool` — result returned to the model after a tool call.\n */\nexport type Message =\n | { role: 'user'; content: string | ContentBlock[] }\n | { role: 'assistant'; content?: string; toolCalls?: ToolCall[] }\n | { role: 'tool'; toolCallId: string; content: string }\n\n/**\n * Normalize user-message content to blocks.\n *\n * A string becomes one text block (an empty string stays one empty text block).\n * An array is returned as-is, same reference, with no validation.\n */\nexport function toBlocks(content: string | ContentBlock[]): ContentBlock[] {\n return typeof content === 'string' ? [{ type: 'text', text: content }] : content\n}\n\n/**\n * A tool the LLM may invoke during a conversation turn.\n *\n * `inputSchema` must be a valid JSON Schema object describing the tool's\n * input parameters (LLM adapters pass it to the provider as-is).\n */\nexport interface Tool {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n}\n\n/**\n * A single tool-call request emitted by the LLM in an assistant turn.\n *\n * `id` is provider-assigned and must be echoed back in the corresponding\n * `role: 'tool'` message so the model can correlate results.\n */\nexport interface ToolCall {\n id: string\n name: string\n input: unknown\n}\n\n/**\n * Normalized response returned by every {@link LLM} adapter.\n *\n * `text` may be empty when `stopReason` is `'tool_use'`.\n * `toolCalls` is empty when `stopReason` is `'end'` or `'max_tokens'`.\n * `usage` is always present — all adapters must populate it.\n */\nexport interface LLMResponse {\n text: string\n toolCalls: ToolCall[]\n stopReason: 'end' | 'tool_use' | 'max_tokens'\n usage: {\n /** Number of input tokens consumed by this call. */\n inputTokens: number\n /** Number of output tokens generated by this call. */\n outputTokens: number\n /**\n * The model's total context window capacity in tokens.\n * Present only when the adapter can determine it.\n * Absent when the adapter cannot determine capacity.\n */\n contextWindowSize?: number\n }\n}\n\n/**\n * Payload emitted by LLM adapters as an `\"llm.request\"` observer event,\n * immediately before the provider call is made.\n *\n * Each `invoke()` call that reaches this event is followed by exactly one of\n * `\"llm.response\"` ({@link LLMUsageEvent}) or `\"llm.error\"` ({@link LLMErrorEvent}),\n * carrying the same `callId`. A throw before this event emits nothing.\n *\n * `messages` and `tools` are opt-in — they may contain sensitive data. When present they\n * are the normalized harness shapes passed to `invoke()`, not the provider-translated\n * payloads. They are passed by reference; observers must not mutate them.\n */\nexport interface LLMRequestEvent {\n /**\n * Correlation id for this single `invoke()` call, minted per call by the adapter and\n * carried on the `\"llm.request\"` event and on the matching `\"llm.response\"` or\n * `\"llm.error\"` event, so an observer can pair them reliably even when concurrent\n * fork branches interleave.\n */\n callId: string\n modelId: string\n providerName: string\n messages?: Message[] // opt-in; the messages passed to invoke()\n tools?: Tool[] // opt-in; the tools passed to invoke()\n}\n\n/**\n * Token-usage payload emitted by LLM adapters as an `\"llm.response\"` observer event,\n * after a successful provider call.\n *\n * Adapters that cannot report real token counts (e.g. mock adapters) emit `{ input: 0, output: 0 }`.\n *\n * `output` is opt-in — it may contain sensitive data; adapters default to omitting it. When\n * present it is the normalized {@link LLMResponse} that `invoke()` returns, passed by reference.\n * `contextWindowSize` mirrors `LLMResponse.usage.contextWindowSize` — present when the\n * adapter can determine it; absent otherwise.\n */\nexport interface LLMUsageEvent {\n /** Correlation id matching the `\"llm.request\"` event for the same `invoke()` call (see {@link LLMRequestEvent.callId}). */\n callId: string\n tokens: { input: number; output: number }\n modelId: string\n stopReason: LLMResponse['stopReason']\n providerName: string\n output?: LLMResponse // opt-in; may be sensitive\n contextWindowSize?: number\n}\n\n/**\n * Payload emitted by LLM adapters as an `\"llm.error\"` observer event, when anything throws\n * after `\"llm.request\"` was emitted and before `\"llm.response\"` would be (the provider call,\n * response normalization). The adapter then rethrows the same value.\n */\nexport interface LLMErrorEvent {\n /** Correlation id matching the `\"llm.request\"` event for the same `invoke()` call (see {@link LLMRequestEvent.callId}). */\n callId: string\n modelId: string\n providerName: string\n /** The thrown value, unchanged; any value, including `undefined`. */\n error: unknown\n}\n\n/**\n * Payload emitted on `\"tool.call\"` before a tool is dispatched.\n *\n * `input` is opt-in — it may contain sensitive data; omit by default.\n */\nexport interface ToolCallEvent {\n toolName: string\n toolCallId: string\n input?: unknown\n}\n\n/**\n * Payload emitted on `\"tool.result\"` after a tool completes or throws.\n *\n * `result` is opt-in — it may contain sensitive data; omit by default.\n * `error` is set whenever the tool threw (the thrown value) and absent on success; it is how\n * consumers tell a failed call from a successful one, so it is not gated.\n */\nexport interface ToolResultEvent {\n toolName: string\n toolCallId: string\n durationMs: number\n result?: unknown\n error?: unknown\n}\n\n/**\n * Core interface every LLM adapter must implement.\n *\n * @example\n * ```ts\n * const response = await llm.invoke(messages, { tools: [myTool] })\n * ```\n */\nexport interface LLM {\n /**\n * Send a message list to the LLM and return a normalized response.\n *\n * @param messages - Conversation history, oldest first.\n * @param options.tools - Optional tools the model may invoke.\n */\n invoke(messages: Message[], options?: { tools?: Tool[] }): Promise<LLMResponse>\n}\n\nexport { runTool, type ToolRunContext, type RunToolOptions } from './run-tool.js'\n"],"mappings":";AAgCA,eAAsB,QACpB,KACA,MACA,IACA,SACY;AACZ,QAAM,YAAY,SAAS,cAAc;AACzC,QAAM,OAAO,EAAE,UAAU,KAAK,MAAM,YAAY,KAAK,GAAG;AACxD,QAAM,YAA2B,YAAY,EAAE,GAAG,MAAM,OAAO,KAAK,MAAM,IAAI;AAC9E,MAAI,KAAK,aAAa,SAAS;AAE/B,QAAM,QAAQ,KAAK,IAAI;AACvB,MAAI;AACJ,MAAI;AACF,YAAQ,MAAM,IAAI,OAAO,aAAa,EAAE,YAAY,KAAK,GAAG,GAAG,EAAE;AAAA,EACnE,SAAS,OAAO;AACd,UAAM,SAA0B;AAAA,MAC9B,GAAG;AAAA,MACH,YAAY,KAAK,IAAI,IAAI;AAAA;AAAA,MAEzB,OAAO,UAAU,SAAY,IAAI,MAAM,sBAAsB,IAAI;AAAA,IACnE;AACA,QAAI,KAAK,eAAe,MAAM;AAC9B,UAAM;AAAA,EACR;AACA,QAAM,YAA6B,YAC/B,EAAE,GAAG,MAAM,YAAY,KAAK,IAAI,IAAI,OAAO,QAAQ,MAAM,IACzD,EAAE,GAAG,MAAM,YAAY,KAAK,IAAI,IAAI,MAAM;AAC9C,MAAI,KAAK,eAAe,SAAS;AACjC,SAAO;AACT;;;ACvBO,SAAS,SAAS,SAAkD;AACzE,SAAO,OAAO,YAAY,WAAW,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,CAAC,IAAI;AAC3E;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/run-tool.ts","../src/index.ts"],"sourcesContent":["import type { ToolCall, ToolCallEvent, ToolResultEvent } from './index.js'\n\n/**\n * The part of a step ctx {@link runTool} needs. The `@noetaris/harness` step ctx (0.7 and\n * later) satisfies it structurally.\n */\nexport interface ToolRunContext {\n emit(name: string, payload?: unknown): void\n within<T>(type: string, payload: unknown, fn: () => Promise<T>): Promise<T>\n}\n\nexport interface RunToolOptions {\n /**\n * Put the tool's `input` on `\"tool.call\"` and its `result` on `\"tool.result\"`. They may\n * contain sensitive data. Default: `false`.\n */\n captureIO?: boolean\n}\n\n/**\n * Run one tool call and report it: emits `\"tool.call\"`, runs `fn` inside the observer's scope\n * for that event (`ctx.within('tool.call', { toolCallId })`), then emits `\"tool.result\"` with\n * `durationMs` — and `error` when `fn` threw, in which case the same value is rethrown.\n * Returns `fn`'s value.\n *\n * @example\n * ```ts\n * const outputs = await Promise.all(\n * res.toolCalls.map(call => runTool(ctx, call, () => toolImpls[call.name](call.input))),\n * )\n * ```\n */\nexport async function runTool<T>(\n ctx: ToolRunContext,\n call: Pick<ToolCall, 'id' | 'name' | 'input'>,\n fn: () => Promise<T>,\n options?: RunToolOptions,\n): Promise<T> {\n const captureIO = options?.captureIO === true\n const base = { toolName: call.name, toolCallId: call.id }\n const callEvent: ToolCallEvent = captureIO ? { ...base, input: call.input } : base\n ctx.emit('tool.call', callEvent)\n\n const start = Date.now()\n let value: T\n try {\n value = await ctx.within('tool.call', { toolCallId: call.id }, fn)\n } catch (error) {\n const failed: ToolResultEvent = {\n ...base,\n durationMs: Date.now() - start,\n // Consumers read `error !== undefined` as \"the tool failed\", so a thrown undefined is named.\n error: error === undefined ? new Error('tool threw undefined') : error,\n }\n ctx.emit('tool.result', failed)\n throw error\n }\n const succeeded: ToolResultEvent = captureIO\n ? { ...base, durationMs: Date.now() - start, result: value }\n : { ...base, durationMs: Date.now() - start }\n ctx.emit('tool.result', succeeded)\n return value\n}\n","/**\n * Shared LLM protocol types used by all harness LLM adapters.\n *\n * @packageDocumentation\n */\n\n/** A run of plain text inside a user message or a system prompt. */\nexport type TextBlock = {\n type: 'text'\n text: string\n /**\n * Marks the end of a cacheable prefix: the provider may cache everything up to and including\n * this block (the system prompt first, then the messages in order). Adapters whose provider\n * caches only at marked points (Anthropic) send it; adapters whose provider caches\n * automatically ignore it. A hint, not a guarantee: the provider decides whether a cache\n * entry is written or read (e.g. a minimum prefix length). Anthropic allows at most 4 points\n * per request (its `autoCache` option takes one of them); its adapter throws before\n * `\"llm.request\"` when there are more. See the adapter's README.\n */\n cachePoint?: true\n}\n\n/**\n * An image inside a user message.\n *\n * `data` is raw base64 with no `data:` prefix. `mediaType` is the MIME type\n * (e.g. `'image/png'`); Anthropic, OpenAI and Ollama throw on types they do not accept; Gemini passes any value through.\n */\nexport type ImageBlock = {\n type: 'image'\n data: string\n mediaType: string\n /**\n * Marks the end of a cacheable prefix: the provider may cache everything up to and including\n * this block (the system prompt first, then the messages in order). Adapters whose provider\n * caches only at marked points (Anthropic) send it; adapters whose provider caches\n * automatically ignore it. A hint, not a guarantee: the provider decides whether a cache\n * entry is written or read (e.g. a minimum prefix length). Anthropic allows at most 4 points\n * per request (its `autoCache` option takes one of them); its adapter throws before\n * `\"llm.request\"` when there are more. See the adapter's README.\n */\n cachePoint?: true\n}\n\n/** One piece of user-message content. */\nexport type ContentBlock = TextBlock | ImageBlock\n\n/**\n * A single turn in a conversation.\n *\n * - `user` — input from the human side: a string, or blocks mixing text and images.\n * An empty block array is allowed by the type but may be rejected by providers; don't send one.\n * - `assistant` — model reply, optionally carrying tool-call requests.\n * - `tool` — result returned to the model after a tool call.\n */\nexport type Message =\n | { role: 'user'; content: string | ContentBlock[] }\n | { role: 'assistant'; content?: string; toolCalls?: ToolCall[] }\n | { role: 'tool'; toolCallId: string; content: string }\n\n/**\n * Normalize user-message content to blocks.\n *\n * A string becomes one text block (an empty string stays one empty text block).\n * An array is returned as-is, same reference, with no validation.\n */\nexport function toBlocks(content: string | ContentBlock[]): ContentBlock[] {\n return typeof content === 'string' ? [{ type: 'text', text: content }] : content\n}\n\n/**\n * A tool the LLM may invoke during a conversation turn.\n *\n * `inputSchema` must be a valid JSON Schema object describing the tool's\n * input parameters (LLM adapters pass it to the provider as-is).\n */\nexport interface Tool {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n}\n\n/**\n * A single tool-call request emitted by the LLM in an assistant turn.\n *\n * `id` is provider-assigned and must be echoed back in the corresponding\n * `role: 'tool'` message so the model can correlate results.\n */\nexport interface ToolCall {\n id: string\n name: string\n input: unknown\n}\n\n/**\n * Normalized response returned by every {@link LLM} adapter.\n *\n * `text` may be empty when `stopReason` is `'tool_use'`.\n * `toolCalls` is empty when `stopReason` is `'end'` or `'max_tokens'`.\n * `usage` is always present — all adapters must populate it.\n */\nexport interface LLMResponse {\n text: string\n toolCalls: ToolCall[]\n stopReason: 'end' | 'tool_use' | 'max_tokens'\n usage: {\n /**\n * Total input tokens of this call, cached tokens included. Compare it with\n * `contextWindowSize`. The uncached part, billed at the normal input price, is\n * `inputTokens − (cacheReadTokens ?? 0) − (cacheWriteTokens ?? 0)`.\n */\n inputTokens: number\n /** Number of output tokens generated by this call. */\n outputTokens: number\n /**\n * Input tokens read from the provider's prompt cache. Part of `inputTokens`, not added to\n * it. Absent when the adapter cannot know; `0` is a real count (no cache hit).\n */\n cacheReadTokens?: number\n /**\n * Input tokens written to the provider's prompt cache. Part of `inputTokens`, not added to\n * it. Absent when the adapter cannot know; `0` is a real count.\n */\n cacheWriteTokens?: number\n /**\n * `cacheWriteTokens` split by cache lifetime (TTL). Each key is the provider's TTL label\n * (for example `'5m'`, `'1h'`, `'30m'`); each value is the input tokens written with that\n * TTL. The values add up to `cacheWriteTokens`, which is then present too. Absent when the\n * adapter cannot tell the split. Which keys an adapter reports is in its README; a reported\n * key with `0` is a real count. Keys are strings because TTL labels differ by provider.\n */\n cacheWriteTokensByTtl?: Record<string, number>\n /**\n * The model's total context window capacity in tokens.\n * Present only when the adapter can determine it.\n * Absent when the adapter cannot determine capacity.\n */\n contextWindowSize?: number\n }\n}\n\n/**\n * Payload emitted by LLM adapters as an `\"llm.request\"` observer event,\n * immediately before the provider call is made.\n *\n * Each `invoke()` call that reaches this event is followed by exactly one of\n * `\"llm.response\"` ({@link LLMUsageEvent}) or `\"llm.error\"` ({@link LLMErrorEvent}),\n * carrying the same `callId`. A throw before this event emits nothing.\n *\n * `messages`, `tools` and `system` are opt-in — they may contain sensitive data. When present they\n * are the normalized harness shapes passed to `invoke()`, not the provider-translated\n * payloads. They are passed by reference; observers must not mutate them.\n */\nexport interface LLMRequestEvent {\n /**\n * Correlation id for this single `invoke()` call, minted per call by the adapter and\n * carried on the `\"llm.request\"` event and on the matching `\"llm.response\"` or\n * `\"llm.error\"` event, so an observer can pair them reliably even when concurrent\n * fork branches interleave.\n */\n callId: string\n modelId: string\n providerName: string\n messages?: Message[] // opt-in; the messages passed to invoke()\n tools?: Tool[] // opt-in; the tools passed to invoke()\n system?: string | TextBlock[] // opt-in; the system option passed to invoke()\n}\n\n/**\n * Token-usage payload emitted by LLM adapters as an `\"llm.response\"` observer event,\n * after a successful provider call.\n *\n * `tokens` carries the same numbers as the returned `LLMResponse.usage`: `input` = `inputTokens`,\n * `output` = `outputTokens`, `cacheRead` = `cacheReadTokens`, `cacheWrite` = `cacheWriteTokens`,\n * `cacheWriteByTtl` = `cacheWriteTokensByTtl` (present exactly when the usage field is). Mock\n * adapters report the `usage` of the response they return. `cacheWriteByTtl` may be the same\n * object as in `usage`; observers must not mutate it.\n *\n * `output` is opt-in — it may contain sensitive data; adapters default to omitting it. When\n * present it is the normalized {@link LLMResponse} that `invoke()` returns, passed by reference.\n * `contextWindowSize` mirrors `LLMResponse.usage.contextWindowSize` — present when the\n * adapter can determine it; absent otherwise.\n */\nexport interface LLMUsageEvent {\n /** Correlation id matching the `\"llm.request\"` event for the same `invoke()` call (see {@link LLMRequestEvent.callId}). */\n callId: string\n tokens: { input: number; output: number; cacheRead?: number; cacheWrite?: number; cacheWriteByTtl?: Record<string, number> }\n modelId: string\n stopReason: LLMResponse['stopReason']\n providerName: string\n output?: LLMResponse // opt-in; may be sensitive\n contextWindowSize?: number\n}\n\n/**\n * Payload emitted by LLM adapters as an `\"llm.error\"` observer event, when anything throws\n * after `\"llm.request\"` was emitted and before `\"llm.response\"` would be (the provider call,\n * response normalization). The adapter then rethrows the same value.\n *\n * When the call's `signal` aborted, `error` is `signal.reason`, unchanged. The caller can tell a\n * cancel from a failure reliably with `signal.aborted` (or `error === signal.reason`). An\n * observer, which does not see the signal, can use `error.name` only for the default reasons:\n * `AbortError` from `controller.abort()` with no argument, `TimeoutError` from\n * `AbortSignal.timeout()`; a custom reason (`controller.abort(new Error('x'))`,\n * `controller.abort('x')`) keeps its own name or has none.\n */\nexport interface LLMErrorEvent {\n /** Correlation id matching the `\"llm.request\"` event for the same `invoke()` call (see {@link LLMRequestEvent.callId}). */\n callId: string\n modelId: string\n providerName: string\n /** The thrown value, unchanged; any value, including `undefined`. */\n error: unknown\n}\n\n/**\n * Payload emitted on `\"tool.call\"` before a tool is dispatched.\n *\n * `input` is opt-in — it may contain sensitive data; omit by default.\n */\nexport interface ToolCallEvent {\n toolName: string\n toolCallId: string\n input?: unknown\n}\n\n/**\n * Payload emitted on `\"tool.result\"` after a tool completes or throws.\n *\n * `result` is opt-in — it may contain sensitive data; omit by default.\n * `error` is set whenever the tool threw (the thrown value) and absent on success; it is how\n * consumers tell a failed call from a successful one, so it is not gated.\n */\nexport interface ToolResultEvent {\n toolName: string\n toolCallId: string\n durationMs: number\n result?: unknown\n error?: unknown\n}\n\n/** Options of {@link LLM.invoke}. */\nexport interface InvokeOptions {\n /** Tools the model may call. */\n tools?: Tool[]\n /**\n * Cancels the call. When the signal aborts, the adapter closes the HTTP request and `invoke()`\n * rejects with `signal.reason` (an `AbortError` `DOMException` for `controller.abort()`, a\n * `TimeoutError` for `AbortSignal.timeout()`), not the provider SDK's own error. If the signal\n * is already aborted when `invoke()` is called, it rejects with no observer event. If it aborts\n * after `\"llm.request\"`, `\"llm.error\"` is emitted with `error: signal.reason` first.\n *\n * Aborting frees the caller and closes the connection. It does not guarantee that the provider\n * stops the work or stops billing for it.\n *\n * The harness does not pass the run's signal for you. To let `run.stop()` cut the call, pass it\n * from the step: `ctx.llm.invoke(messages, { signal: ctx.signal })`.\n */\n signal?: AbortSignal\n /**\n * The system prompt for this call. Each adapter sends it in its provider's native system slot.\n * A string, or text blocks (a block may carry `cachePoint`). Not stored in the conversation:\n * pass it on every call.\n */\n system?: string | TextBlock[]\n}\n\n/**\n * Core interface every LLM adapter must implement.\n *\n * @example\n * ```ts\n * const response = await llm.invoke(messages, { tools: [myTool], system: 'Be brief.', signal: ctx.signal })\n * ```\n */\nexport interface LLM {\n /**\n * Send a message list to the LLM and return a normalized response.\n *\n * @param messages - Conversation history, oldest first.\n * @param options - See {@link InvokeOptions}.\n * @param options.tools - Tools the model may call.\n * @param options.signal - Cancels the call; `invoke()` then rejects with `signal.reason`.\n * @param options.system - The system prompt, sent in the provider's native system slot.\n */\n invoke(messages: Message[], options?: InvokeOptions): Promise<LLMResponse>\n}\n\nexport { runTool, type ToolRunContext, type RunToolOptions } from './run-tool.js'\n"],"mappings":";AAgCA,eAAsB,QACpB,KACA,MACA,IACA,SACY;AACZ,QAAM,YAAY,SAAS,cAAc;AACzC,QAAM,OAAO,EAAE,UAAU,KAAK,MAAM,YAAY,KAAK,GAAG;AACxD,QAAM,YAA2B,YAAY,EAAE,GAAG,MAAM,OAAO,KAAK,MAAM,IAAI;AAC9E,MAAI,KAAK,aAAa,SAAS;AAE/B,QAAM,QAAQ,KAAK,IAAI;AACvB,MAAI;AACJ,MAAI;AACF,YAAQ,MAAM,IAAI,OAAO,aAAa,EAAE,YAAY,KAAK,GAAG,GAAG,EAAE;AAAA,EACnE,SAAS,OAAO;AACd,UAAM,SAA0B;AAAA,MAC9B,GAAG;AAAA,MACH,YAAY,KAAK,IAAI,IAAI;AAAA;AAAA,MAEzB,OAAO,UAAU,SAAY,IAAI,MAAM,sBAAsB,IAAI;AAAA,IACnE;AACA,QAAI,KAAK,eAAe,MAAM;AAC9B,UAAM;AAAA,EACR;AACA,QAAM,YAA6B,YAC/B,EAAE,GAAG,MAAM,YAAY,KAAK,IAAI,IAAI,OAAO,QAAQ,MAAM,IACzD,EAAE,GAAG,MAAM,YAAY,KAAK,IAAI,IAAI,MAAM;AAC9C,MAAI,KAAK,eAAe,SAAS;AACjC,SAAO;AACT;;;ACIO,SAAS,SAAS,SAAkD;AACzE,SAAO,OAAO,YAAY,WAAW,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,CAAC,IAAI;AAC3E;","names":[]}
|