@dex-ai/sdk 0.1.30

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.
Files changed (85) hide show
  1. package/README.md +308 -0
  2. package/dist/agent.d.ts +181 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +41 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/context.d.ts +68 -0
  7. package/dist/context.d.ts.map +1 -0
  8. package/dist/context.js +8 -0
  9. package/dist/context.js.map +1 -0
  10. package/dist/create-agent.d.ts +7 -0
  11. package/dist/create-agent.d.ts.map +1 -0
  12. package/dist/create-agent.js +205 -0
  13. package/dist/create-agent.js.map +1 -0
  14. package/dist/extension.d.ts +162 -0
  15. package/dist/extension.d.ts.map +1 -0
  16. package/dist/extension.js +20 -0
  17. package/dist/extension.js.map +1 -0
  18. package/dist/generate.d.ts +10 -0
  19. package/dist/generate.d.ts.map +1 -0
  20. package/dist/generate.js +839 -0
  21. package/dist/generate.js.map +1 -0
  22. package/dist/index.d.ts +26 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +16 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/message.d.ts +89 -0
  27. package/dist/message.d.ts.map +1 -0
  28. package/dist/message.js +17 -0
  29. package/dist/message.js.map +1 -0
  30. package/dist/messages.d.ts +98 -0
  31. package/dist/messages.d.ts.map +1 -0
  32. package/dist/messages.js +339 -0
  33. package/dist/messages.js.map +1 -0
  34. package/dist/model.d.ts +39 -0
  35. package/dist/model.d.ts.map +1 -0
  36. package/dist/model.js +11 -0
  37. package/dist/model.js.map +1 -0
  38. package/dist/provider.d.ts +157 -0
  39. package/dist/provider.d.ts.map +1 -0
  40. package/dist/provider.js +39 -0
  41. package/dist/provider.js.map +1 -0
  42. package/dist/resolve-schema.d.ts +44 -0
  43. package/dist/resolve-schema.d.ts.map +1 -0
  44. package/dist/resolve-schema.js +367 -0
  45. package/dist/resolve-schema.js.map +1 -0
  46. package/dist/schema.d.ts +80 -0
  47. package/dist/schema.d.ts.map +1 -0
  48. package/dist/schema.js +90 -0
  49. package/dist/schema.js.map +1 -0
  50. package/dist/tool-dispatch.d.ts +24 -0
  51. package/dist/tool-dispatch.d.ts.map +1 -0
  52. package/dist/tool-dispatch.js +120 -0
  53. package/dist/tool-dispatch.js.map +1 -0
  54. package/dist/tool-result-cache.d.ts +43 -0
  55. package/dist/tool-result-cache.d.ts.map +1 -0
  56. package/dist/tool-result-cache.js +118 -0
  57. package/dist/tool-result-cache.js.map +1 -0
  58. package/dist/tool.d.ts +96 -0
  59. package/dist/tool.d.ts.map +1 -0
  60. package/dist/tool.js +29 -0
  61. package/dist/tool.js.map +1 -0
  62. package/dist/util.d.ts +26 -0
  63. package/dist/util.d.ts.map +1 -0
  64. package/dist/util.js +104 -0
  65. package/dist/util.js.map +1 -0
  66. package/package.json +41 -0
  67. package/src/agent.ts +235 -0
  68. package/src/context.ts +82 -0
  69. package/src/create-agent.ts +237 -0
  70. package/src/extension.ts +244 -0
  71. package/src/generate.ts +943 -0
  72. package/src/index.ts +113 -0
  73. package/src/message.ts +114 -0
  74. package/src/messages.test.ts +299 -0
  75. package/src/messages.ts +423 -0
  76. package/src/model.ts +43 -0
  77. package/src/provider.ts +187 -0
  78. package/src/resolve-schema.test.ts +351 -0
  79. package/src/resolve-schema.ts +426 -0
  80. package/src/schema.ts +131 -0
  81. package/src/tool-dispatch.ts +166 -0
  82. package/src/tool-result-cache.test.ts +182 -0
  83. package/src/tool-result-cache.ts +164 -0
  84. package/src/tool.ts +110 -0
  85. package/src/util.ts +110 -0
@@ -0,0 +1,423 @@
1
+ /**
2
+ * Messages — a guarded, ordered collection of conversation messages.
3
+ *
4
+ * Wraps the internal message array with controlled mutation methods and
5
+ * validates structural invariants on every write. Direct array mutation
6
+ * (via index assignment, push, splice, etc.) is trapped by a Proxy and
7
+ * throws at runtime — extensions MUST use the provided mutation API.
8
+ *
9
+ * Invariants enforced:
10
+ * 1. System messages appear only at the front (contiguous prefix).
11
+ * 2. First non-system message has role "user".
12
+ * 3. Role alternation: assistant follows user, tool follows assistant.
13
+ * 4. Every tool-call must have a matching tool-result before the next user turn.
14
+ * 5. No orphaned tool-result without a preceding tool-call.
15
+ * 6. Every message has an `id`.
16
+ *
17
+ * NOTE: Trailing tool-calls (at the end of the sequence) are allowed — the
18
+ * generate loop commits the assistant message before dispatching tools.
19
+ */
20
+
21
+ import type { Content, Message, Role, ToolCallContent } from "./message";
22
+
23
+ /* ------------------------------------------------------------------ */
24
+ /* Validation */
25
+ /* ------------------------------------------------------------------ */
26
+
27
+ export interface ValidationError {
28
+ readonly index: number;
29
+ readonly message: string;
30
+ }
31
+
32
+ /**
33
+ * Validate the full message sequence. Returns an empty array if valid.
34
+ */
35
+ export function validateMessages(
36
+ messages: ReadonlyArray<Message>,
37
+ ): ValidationError[] {
38
+ const errors: ValidationError[] = [];
39
+
40
+ if (messages.length === 0) return errors;
41
+
42
+ // 1. System messages only at front.
43
+ let systemEnd = 0;
44
+ for (let i = 0; i < messages.length; i++) {
45
+ if (messages[i]!.role === "system") {
46
+ if (i !== systemEnd) {
47
+ errors.push({
48
+ index: i,
49
+ message: `System message at index ${i} after non-system message at index ${systemEnd}`,
50
+ });
51
+ }
52
+ systemEnd = i + 1;
53
+ }
54
+ }
55
+
56
+ // 2. First non-system message must be user role.
57
+ if (systemEnd < messages.length) {
58
+ const first = messages[systemEnd]!;
59
+ if (first.role !== "user") {
60
+ errors.push({
61
+ index: systemEnd,
62
+ message: `First non-system message must be "user", got "${first.role}"`,
63
+ });
64
+ }
65
+ }
66
+
67
+ // 3. Role alternation + tool-call/tool-result pairing.
68
+ let pendingToolCalls = new Map<string, number>(); // toolCallId → assistant msg index
69
+
70
+ for (let i = systemEnd; i < messages.length; i++) {
71
+ const msg = messages[i]!;
72
+ const prev = i > systemEnd ? messages[i - 1]! : null;
73
+
74
+ switch (msg.role) {
75
+ case "user": {
76
+ // user can follow: nothing (first), assistant, or tool
77
+ if (prev && prev.role !== "assistant" && prev.role !== "tool") {
78
+ errors.push({
79
+ index: i,
80
+ message: `"user" message follows "${prev.role}" (expected after "assistant" or "tool")`,
81
+ });
82
+ }
83
+ // If we still have pending tool calls from a previous assistant, that's an error
84
+ if (pendingToolCalls.size > 0) {
85
+ for (const [id, idx] of pendingToolCalls) {
86
+ errors.push({
87
+ index: idx,
88
+ message: `Orphaned tool-call "${id}" — no matching tool-result before next user turn`,
89
+ });
90
+ }
91
+ pendingToolCalls = new Map();
92
+ }
93
+ break;
94
+ }
95
+ case "assistant": {
96
+ // assistant follows user or tool
97
+ if (prev && prev.role !== "user" && prev.role !== "tool") {
98
+ errors.push({
99
+ index: i,
100
+ message: `"assistant" message follows "${prev.role}" (expected after "user" or "tool")`,
101
+ });
102
+ }
103
+ // Collect tool calls
104
+ for (const c of msg.content) {
105
+ if (c.type === "tool-call") {
106
+ pendingToolCalls.set(
107
+ (c as ToolCallContent).toolCallId,
108
+ i,
109
+ );
110
+ }
111
+ }
112
+ break;
113
+ }
114
+ case "tool": {
115
+ // tool follows assistant or tool
116
+ if (prev && prev.role !== "assistant" && prev.role !== "tool") {
117
+ errors.push({
118
+ index: i,
119
+ message: `"tool" message follows "${prev.role}" (expected after "assistant" or "tool")`,
120
+ });
121
+ }
122
+ // Match tool-results to pending tool-calls
123
+ for (const c of msg.content) {
124
+ if (c.type === "tool-result") {
125
+ const id = c.toolCallId;
126
+ if (!pendingToolCalls.has(id)) {
127
+ errors.push({
128
+ index: i,
129
+ message: `Orphaned tool-result "${id}" — no matching tool-call`,
130
+ });
131
+ } else {
132
+ pendingToolCalls.delete(id);
133
+ }
134
+ }
135
+ }
136
+ break;
137
+ }
138
+ }
139
+ }
140
+
141
+ // NOTE: We intentionally do NOT flag pending tool-calls at the end of the
142
+ // sequence. The generate loop commits the assistant message (with tool-calls)
143
+ // before dispatching tools and committing their results. This intermediate
144
+ // state is valid — the tool-results will arrive on subsequent appends.
145
+ // The real invariant violation (tool-calls that never get results) is caught
146
+ // at line 81-89 when a "user" message arrives while tool-calls are still
147
+ // pending.
148
+
149
+ return errors;
150
+ }
151
+
152
+ /* ------------------------------------------------------------------ */
153
+ /* Messages class */
154
+ /* ------------------------------------------------------------------ */
155
+
156
+ /**
157
+ * Mutation mode controls when validation runs:
158
+ * - "strict": validate after every mutation, throw on failure
159
+ * - "batch": defer validation until `commit()` is called
160
+ */
161
+ type MutationMode = "strict" | "batch";
162
+
163
+ const TRAPPED_MUTATORS = new Set([
164
+ "push",
165
+ "pop",
166
+ "shift",
167
+ "unshift",
168
+ "splice",
169
+ "sort",
170
+ "reverse",
171
+ "fill",
172
+ "copyWithin",
173
+ ]);
174
+
175
+ export class Messages {
176
+ private readonly _internal: Message[];
177
+ private readonly _proxy: ReadonlyArray<Message>;
178
+ private _batchDepth = 0;
179
+
180
+ constructor(initial?: ReadonlyArray<Message>) {
181
+ this._internal = initial ? [...initial] : [];
182
+
183
+ // Create a Proxy that traps all mutating operations
184
+ this._proxy = new Proxy(this._internal, {
185
+ set(_target, prop, _value) {
186
+ // Allow length to be read but not set externally
187
+ if (prop === "length") {
188
+ throw new Error(
189
+ "[Messages] Direct array mutation is not allowed. " +
190
+ "Use messages.splice() or messages.append() instead.",
191
+ );
192
+ }
193
+ // Block numeric index assignment
194
+ if (typeof prop === "string" && /^\d+$/.test(prop)) {
195
+ throw new Error(
196
+ "[Messages] Direct index assignment is not allowed. " +
197
+ "Use messages.replace() instead.",
198
+ );
199
+ }
200
+ return false;
201
+ },
202
+ get(target, prop, receiver) {
203
+ // Trap mutating array methods
204
+ if (typeof prop === "string" && TRAPPED_MUTATORS.has(prop)) {
205
+ return () => {
206
+ throw new Error(
207
+ `[Messages] Direct .${prop}() is not allowed. ` +
208
+ "Use the Messages API (append, splice, replaceRange) instead.",
209
+ );
210
+ };
211
+ }
212
+ return Reflect.get(target, prop, receiver);
213
+ },
214
+ deleteProperty(_target, prop) {
215
+ throw new Error(
216
+ `[Messages] Cannot delete index ${String(prop)}. ` +
217
+ "Use messages.splice() instead.",
218
+ );
219
+ },
220
+ }) as ReadonlyArray<Message>;
221
+ }
222
+
223
+ /* ---------------------------------------------------------------- */
224
+ /* Read access */
225
+ /* ---------------------------------------------------------------- */
226
+
227
+ /** The proxied read-only view. Safe to expose on AgentContext. */
228
+ get array(): ReadonlyArray<Message> {
229
+ return this._proxy;
230
+ }
231
+
232
+ get length(): number {
233
+ return this._internal.length;
234
+ }
235
+
236
+ at(index: number): Message | undefined {
237
+ return this._internal.at(index);
238
+ }
239
+
240
+ /* ---------------------------------------------------------------- */
241
+ /* Controlled mutations */
242
+ /* ---------------------------------------------------------------- */
243
+
244
+ /**
245
+ * Append one or more messages. Assigns IDs if missing.
246
+ * Validates invariants after append (unless in a batch).
247
+ */
248
+ append(message: Message | ReadonlyArray<Message>): void {
249
+ const msgs = Array.isArray(message) ? message : [message];
250
+ const prepared: Message[] = [];
251
+ for (const m of msgs) {
252
+ const withId: Message =
253
+ m.id !== undefined ? m : { ...m, id: crypto.randomUUID() };
254
+ prepared.push(withId);
255
+ }
256
+ this._internal.push(...prepared);
257
+ this._validateIfStrict();
258
+ }
259
+
260
+ /**
261
+ * Replace a range of messages. Like Array.splice but validates afterward.
262
+ * Returns the removed messages.
263
+ */
264
+ splice(
265
+ start: number,
266
+ deleteCount: number,
267
+ ...items: Message[]
268
+ ): Message[] {
269
+ // Assign IDs to inserted items
270
+ const prepared = items.map((m) =>
271
+ m.id !== undefined ? m : { ...m, id: crypto.randomUUID() },
272
+ );
273
+ const removed = this._internal.splice(start, deleteCount, ...prepared);
274
+ this._validateIfStrict();
275
+ return removed;
276
+ }
277
+
278
+ /**
279
+ * Replace a contiguous range with new messages.
280
+ * Convenience wrapper over splice.
281
+ */
282
+ replaceRange(
283
+ start: number,
284
+ end: number,
285
+ replacement: ReadonlyArray<Message>,
286
+ ): Message[] {
287
+ const deleteCount = end - start;
288
+ return this.splice(start, deleteCount, ...(replacement as Message[]));
289
+ }
290
+
291
+ /**
292
+ * Replace a single message at an index.
293
+ */
294
+ replace(index: number, message: Message): void {
295
+ if (index < 0 || index >= this._internal.length) {
296
+ throw new RangeError(
297
+ `[Messages] Index ${index} out of bounds (length=${this._internal.length})`,
298
+ );
299
+ }
300
+ const withId: Message =
301
+ message.id !== undefined ? message : { ...message, id: crypto.randomUUID() };
302
+ this._internal[index] = withId;
303
+ this._validateIfStrict();
304
+ }
305
+
306
+ /**
307
+ * Set the length of the internal array (truncation).
308
+ * Used for reordering during agent creation.
309
+ */
310
+ truncate(length: number): void {
311
+ if (length < 0) {
312
+ throw new RangeError(`[Messages] Cannot truncate to negative length`);
313
+ }
314
+ this._internal.length = length;
315
+ this._validateIfStrict();
316
+ }
317
+
318
+ /**
319
+ * Direct push without validation — used only by the internal
320
+ * reorder step during agent creation where system messages are
321
+ * moved to front. Caller MUST call validate() afterward.
322
+ */
323
+ _unsafePush(...messages: Message[]): void {
324
+ this._internal.push(...messages);
325
+ }
326
+
327
+ /**
328
+ * Set length without validation — used only during agent creation reorder.
329
+ */
330
+ _unsafeSetLength(length: number): void {
331
+ this._internal.length = length;
332
+ }
333
+
334
+ /* ---------------------------------------------------------------- */
335
+ /* Batch mode */
336
+ /* ---------------------------------------------------------------- */
337
+
338
+ /**
339
+ * Begin a batch — validation is deferred until `commit()`.
340
+ * Batches can nest; validation runs when the outermost batch commits.
341
+ */
342
+ batch(): void {
343
+ this._batchDepth++;
344
+ }
345
+
346
+ /**
347
+ * End a batch and validate. Throws if invariants are violated.
348
+ * On failure the messages are left as-is (caller must fix or rollback).
349
+ */
350
+ commit(): void {
351
+ if (this._batchDepth <= 0) {
352
+ throw new Error("[Messages] commit() called without matching batch()");
353
+ }
354
+ this._batchDepth--;
355
+ if (this._batchDepth === 0) {
356
+ this._validate();
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Perform a batch operation with automatic commit.
362
+ * If the function throws, the batch is still ended (but not validated).
363
+ */
364
+ transaction(fn: () => void): void {
365
+ this.batch();
366
+ try {
367
+ fn();
368
+ this.commit();
369
+ } catch (err) {
370
+ this._batchDepth = Math.max(0, this._batchDepth - 1);
371
+ throw err;
372
+ }
373
+ }
374
+
375
+ /* ---------------------------------------------------------------- */
376
+ /* Validation */
377
+ /* ---------------------------------------------------------------- */
378
+
379
+ /**
380
+ * Explicitly validate the current state. Returns errors or throws.
381
+ */
382
+ validate(): ValidationError[] {
383
+ return validateMessages(this._internal);
384
+ }
385
+
386
+ /**
387
+ * Validate and throw if invalid.
388
+ */
389
+ assertValid(): void {
390
+ const errors = this.validate();
391
+ if (errors.length > 0) {
392
+ const details = errors
393
+ .slice(0, 5)
394
+ .map((e) => ` [${e.index}] ${e.message}`)
395
+ .join("\n");
396
+ const suffix =
397
+ errors.length > 5 ? `\n ... and ${errors.length - 5} more` : "";
398
+ throw new Error(
399
+ `[Messages] Invalid message sequence (${errors.length} error(s)):\n${details}${suffix}`,
400
+ );
401
+ }
402
+ }
403
+
404
+ private _validateIfStrict(): void {
405
+ if (this._batchDepth > 0) return;
406
+ this._validate();
407
+ }
408
+
409
+ private _validate(): void {
410
+ const errors = validateMessages(this._internal);
411
+ if (errors.length > 0) {
412
+ const details = errors
413
+ .slice(0, 5)
414
+ .map((e) => ` [${e.index}] ${e.message}`)
415
+ .join("\n");
416
+ const suffix =
417
+ errors.length > 5 ? `\n ... and ${errors.length - 5} more` : "";
418
+ throw new Error(
419
+ `[Messages] Invalid message sequence (${errors.length} error(s)):\n${details}${suffix}`,
420
+ );
421
+ }
422
+ }
423
+ }
package/src/model.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Model — a callable model instance registered by a provider extension.
3
+ *
4
+ * The extension pre-binds connection config (baseUrl, apiKey, protocol logic)
5
+ * into the `stream` method. The loop just calls `model.stream(req)`.
6
+ *
7
+ * Provider packages own the wire translation (SDK shapes ↔ API-specific JSON).
8
+ * The SDK only defines the interface — it never imports protocol-specific code.
9
+ */
10
+
11
+ import type { StreamPart } from "./provider";
12
+ import type { ModelRequest } from "./provider";
13
+
14
+ export type ThinkingLevel = "off" | "min" | "low" | "med" | "high" | "max";
15
+
16
+ export interface Model {
17
+ /** Model identifier within the provider extension, e.g. 'gpt-4.1'. */
18
+ readonly id: string;
19
+ /** Display name for UIs. */
20
+ readonly name?: string;
21
+ /** Maximum context window in tokens. */
22
+ readonly contextWindow?: number;
23
+ /** Maximum output tokens per response. */
24
+ readonly maxTokens?: number;
25
+ /** Whether this model supports extended thinking / chain-of-thought. */
26
+ readonly reasoning?: boolean;
27
+ /** Available thinking levels for this model. Empty or undefined means no thinking support. */
28
+ readonly thinkingLevels?: ReadonlyArray<ThinkingLevel>;
29
+ /** Supported input modalities. */
30
+ readonly input?: ReadonlyArray<"text" | "image">;
31
+
32
+ /**
33
+ * Stream a completion. The provider extension pre-binds auth, baseUrl, and
34
+ * protocol translation into this method. The loop calls it directly.
35
+ */
36
+ stream(req: ModelRequest): AsyncIterable<StreamPart>;
37
+
38
+ /**
39
+ * Optional: verify the model is reachable. Throws on failure.
40
+ * Provider extensions can implement this by hitting a lightweight endpoint.
41
+ */
42
+ ping?(): Promise<void>;
43
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Provider — the model boundary.
3
+ *
4
+ * Typed, streaming-first (ai-sdk lean). StreamPart is what the PROVIDER
5
+ * emits — provider-only, no synthetic parts. Every provider-level span
6
+ * has a start/stop pair so tracers can build spans from the stream:
7
+ *
8
+ * response-start ── begins a provider call
9
+ * message-start ── begins the assistant message
10
+ * text-delta
11
+ * reasoning-delta
12
+ * tool-call-delta
13
+ * tool-call
14
+ * message-stop
15
+ * finish ── terminal accounting for this response
16
+ * response-stop ── ends a provider call
17
+ *
18
+ * raw-chunk ── opaque wire payload (optional)
19
+ * error, abort ── control
20
+ *
21
+ * Loop-level lifecycle (iteration boundaries, tool-execute spans) is NOT
22
+ * part of StreamPart. That flows through extension hooks only.
23
+ */
24
+
25
+ import type { Message } from "./message";
26
+ import type { AnyTool } from "./tool";
27
+ import type { ThinkingLevel } from "./model";
28
+
29
+ export type FinishReason =
30
+ | "stop"
31
+ | "length"
32
+ | "tool-calls"
33
+ | "content-filter"
34
+ | "error"
35
+ | "abort";
36
+
37
+ export interface Usage {
38
+ readonly inputTokens: number;
39
+ readonly outputTokens: number;
40
+ readonly totalTokens?: number;
41
+ readonly cachedInputTokens?: number;
42
+ readonly cacheCreationInputTokens?: number;
43
+ readonly reasoningTokens?: number;
44
+ }
45
+
46
+ export type ToolChoice =
47
+ | "auto"
48
+ | "required"
49
+ | "none"
50
+ | { readonly toolName: string };
51
+
52
+ export interface ModelRequest {
53
+ readonly messages: ReadonlyArray<Message>;
54
+ readonly tools?: ReadonlyArray<AnyTool>;
55
+ readonly toolChoice?: ToolChoice;
56
+ readonly temperature?: number;
57
+ readonly topP?: number;
58
+ readonly maxTokens?: number;
59
+ readonly stopSequences?: ReadonlyArray<string>;
60
+ readonly seed?: number;
61
+ readonly signal?: AbortSignal;
62
+ readonly providerOptions?: Readonly<Record<string, unknown>>;
63
+ /**
64
+ * Enable extended thinking / chain-of-thought reasoning.
65
+ * Pass a ThinkingLevel or `{ budgetTokens: N }` for explicit budget.
66
+ * The provider maps levels to token budgets.
67
+ */
68
+ readonly thinking?: ThinkingLevel | { readonly budgetTokens: number };
69
+ /**
70
+ * Cache breakpoint hints for providers that support explicit prompt caching.
71
+ * Each entry is an index into `messages` whose last content block should be
72
+ * marked as a cache breakpoint. Providers that don't support explicit caching
73
+ * (e.g. OpenAI, which auto-caches by prefix) ignore this field.
74
+ */
75
+ readonly cacheBreakpoints?: ReadonlyArray<number>;
76
+ }
77
+
78
+ export interface ResponseMeta {
79
+ readonly id?: string;
80
+ readonly providerName: string;
81
+ readonly modelId: string;
82
+ readonly startedAt: number;
83
+ readonly endedAt?: number;
84
+ readonly providerMetadata?: Readonly<Record<string, unknown>>;
85
+ }
86
+
87
+ /* -------------------------- StreamPart (provider-emitted) ---------- */
88
+
89
+ export type ResponseStartPart = {
90
+ readonly type: "response-start";
91
+ readonly meta: ResponseMeta;
92
+ };
93
+ export type ResponseStopPart = {
94
+ readonly type: "response-stop";
95
+ readonly meta: ResponseMeta;
96
+ readonly usage: Usage;
97
+ readonly finishReason: FinishReason;
98
+ };
99
+ export type MessageStartPart = {
100
+ readonly type: "message-start";
101
+ readonly role: "assistant";
102
+ };
103
+ export type MessageStopPart = {
104
+ readonly type: "message-stop";
105
+ readonly message: Message;
106
+ };
107
+ export type TextDeltaPart = {
108
+ readonly type: "text-delta";
109
+ readonly delta: string;
110
+ };
111
+ export type ReasoningDeltaPart = {
112
+ readonly type: "reasoning-delta";
113
+ readonly delta: string;
114
+ };
115
+ export type ToolCallDeltaPart = {
116
+ readonly type: "tool-call-delta";
117
+ readonly toolCallId: string;
118
+ readonly toolName: string;
119
+ readonly inputDelta: string;
120
+ };
121
+ export type ToolCallPart = {
122
+ readonly type: "tool-call";
123
+ readonly toolCallId: string;
124
+ readonly toolName: string;
125
+ readonly input: unknown;
126
+ };
127
+ export type FinishPart = {
128
+ readonly type: "finish";
129
+ readonly reason: FinishReason;
130
+ readonly usage: Usage;
131
+ };
132
+ export type RawChunkPart = {
133
+ readonly type: "raw-chunk";
134
+ readonly providerName: string;
135
+ readonly data: unknown;
136
+ };
137
+ export type ErrorPart = {
138
+ readonly type: "error";
139
+ readonly error: unknown;
140
+ readonly recoverable?: boolean;
141
+ };
142
+ export type AbortPart = { readonly type: "abort"; readonly reason?: unknown };
143
+
144
+ export type StreamPart =
145
+ | ResponseStartPart
146
+ | ResponseStopPart
147
+ | MessageStartPart
148
+ | MessageStopPart
149
+ | TextDeltaPart
150
+ | ReasoningDeltaPart
151
+ | ToolCallDeltaPart
152
+ | ToolCallPart
153
+ | FinishPart
154
+ | RawChunkPart
155
+ | ErrorPart
156
+ | AbortPart;
157
+
158
+ export interface ModelResponse {
159
+ readonly message: Message;
160
+ readonly usage: Usage;
161
+ readonly finishReason: FinishReason;
162
+ readonly meta: ResponseMeta;
163
+ readonly raw?: unknown;
164
+ }
165
+
166
+ export interface Provider {
167
+ readonly name: string;
168
+ readonly modelId: string;
169
+ generate(req: ModelRequest): Promise<ModelResponse>;
170
+ stream(req: ModelRequest): AsyncIterable<StreamPart>;
171
+ }
172
+
173
+ /**
174
+ * Provider namespace — identity-typed declaration helper.
175
+ *
176
+ * Use `Provider.define({...})` when implementing a custom provider to
177
+ * get full contextual typing. Runtime behavior: returns the argument
178
+ * unchanged. Note that concrete provider factories (e.g. `openai()`
179
+ * from `@dex-ai/openai`) are separate; this helper is only useful
180
+ * when authoring your own provider.
181
+ */
182
+ // eslint-disable-next-line @typescript-eslint/no-redeclare
183
+ export const Provider = {
184
+ define<P extends Provider>(provider: P): P {
185
+ return provider;
186
+ },
187
+ };