@tanstack/ai 0.28.0 → 0.31.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/dist/esm/activities/chat/adapter.d.ts +10 -0
- package/dist/esm/activities/chat/adapter.js +1 -0
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +3 -2
- package/dist/esm/activities/chat/index.js +23 -11
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +1 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.d.ts +46 -0
- package/dist/esm/activities/chat/middleware/builder.js +17 -0
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -0
- package/dist/esm/activities/chat/middleware/capabilities.d.ts +93 -0
- package/dist/esm/activities/chat/middleware/capabilities.js +45 -0
- package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -0
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -0
- package/dist/esm/activities/chat/middleware/compose.js +47 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.d.ts +20 -0
- package/dist/esm/activities/chat/middleware/define.js +7 -0
- package/dist/esm/activities/chat/middleware/define.js.map +1 -0
- package/dist/esm/activities/chat/middleware/index.d.ts +8 -0
- package/dist/esm/activities/chat/middleware/types.d.ts +51 -0
- package/dist/esm/activities/chat/middleware/validate.d.ts +19 -0
- package/dist/esm/activities/chat/middleware/validate.js +30 -0
- package/dist/esm/activities/chat/middleware/validate.js.map +1 -0
- package/dist/esm/activities/chat/stream/message-updaters.js +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +6 -0
- package/dist/esm/activities/chat/stream/processor.js +18 -3
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +2 -1
- package/dist/esm/activities/generateVideo/index.js +12 -2
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger/console-logger.d.ts +18 -0
- package/dist/esm/logger/console-logger.js +64 -8
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/types.d.ts +4 -4
- package/dist/esm/middlewares/otel.js +40 -12
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +1 -3
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/types.d.ts +7 -1
- package/package.json +10 -2
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -2
- package/skills/ai-core/media-generation/SKILL.md +29 -1
- package/src/activities/chat/adapter.ts +11 -0
- package/src/activities/chat/index.ts +34 -14
- package/src/activities/chat/messages.ts +1 -0
- package/src/activities/chat/middleware/builder.ts +109 -0
- package/src/activities/chat/middleware/capabilities.ts +162 -0
- package/src/activities/chat/middleware/compose.ts +51 -0
- package/src/activities/chat/middleware/define.ts +34 -0
- package/src/activities/chat/middleware/index.ts +20 -0
- package/src/activities/chat/middleware/types.ts +60 -0
- package/src/activities/chat/middleware/validate.ts +55 -0
- package/src/activities/chat/stream/message-updaters.ts +1 -1
- package/src/activities/chat/stream/processor.ts +30 -3
- package/src/activities/generateVideo/index.ts +12 -0
- package/src/index.ts +14 -0
- package/src/logger/console-logger.ts +112 -17
- package/src/logger/types.ts +4 -4
- package/src/middlewares/otel.ts +57 -12
- package/src/realtime/index.ts +1 -3
- package/src/types.ts +7 -0
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
import type { Logger } from './types'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* `util.inspect` options used with `console.dir` on Node so deeply nested
|
|
5
|
+
* structures (e.g. provider chunk payloads with `usage`, `output`,
|
|
6
|
+
* `reasoning`, `tools`) render in full instead of truncating to
|
|
7
|
+
* `[Object]` / `[Array]`.
|
|
8
|
+
*/
|
|
9
|
+
const DIR_OPTIONS = { depth: null, colors: true } as const
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How `meta` should be rendered on the current runtime:
|
|
13
|
+
*
|
|
14
|
+
* - `dir` — Node. `console.dir(meta, { depth: null, colors: true })` gives a
|
|
15
|
+
* depth-unlimited, colored inspect dump.
|
|
16
|
+
* - `json` — Cloudflare Workers / workerd. workerd never forwards
|
|
17
|
+
* `console.dir` output to the terminal (with or without options), and its
|
|
18
|
+
* own inspect of extra console arguments truncates nested objects, so the
|
|
19
|
+
* payload is appended as circular-safe pretty-printed JSON instead.
|
|
20
|
+
* - `arg` — everything else (browsers, Deno, Bun). `meta` is passed as an
|
|
21
|
+
* extra console argument: devtools keep collapsible object trees and the
|
|
22
|
+
* runtime's inspect handles circular references natively.
|
|
23
|
+
*/
|
|
24
|
+
type MetaStrategy = 'dir' | 'json' | 'arg'
|
|
25
|
+
|
|
26
|
+
function resolveMetaStrategy(): MetaStrategy {
|
|
27
|
+
// workerd must be detected before the Node check: under the `nodejs_compat`
|
|
28
|
+
// flag it emulates `process.versions.node`, but still drops `console.dir`.
|
|
29
|
+
try {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- navigator is missing on Node < 21 despite the DOM lib typing it as always present
|
|
31
|
+
if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') return 'json'
|
|
32
|
+
} catch {
|
|
33
|
+
// A locked-down runtime with a throwing `userAgent` getter is not workerd;
|
|
34
|
+
// fall through to the remaining checks rather than crash the log call.
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
typeof process !== 'undefined' &&
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a partial process global (bundler shims) may lack versions
|
|
39
|
+
typeof process.versions?.node === 'string'
|
|
40
|
+
) {
|
|
41
|
+
return 'dir'
|
|
42
|
+
}
|
|
43
|
+
return 'arg'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* `JSON.stringify` hardened for debug payloads: circular references collapse
|
|
48
|
+
* to `"[Circular]"`, `Error` instances expand to `name`/`message`/`stack`
|
|
49
|
+
* (they would otherwise stringify to `{}`), and `bigint` values become
|
|
50
|
+
* strings (they would otherwise throw). Never throws — falls back to
|
|
51
|
+
* `String(value)` and, if even that coercion throws, a placeholder.
|
|
52
|
+
*/
|
|
53
|
+
function stringifyMetaSafely(value: unknown): string {
|
|
54
|
+
const seen = new WeakSet<object>()
|
|
55
|
+
try {
|
|
56
|
+
return JSON.stringify(
|
|
57
|
+
value,
|
|
58
|
+
(_key, entry: unknown) => {
|
|
59
|
+
if (typeof entry === 'bigint') return entry.toString()
|
|
60
|
+
if (entry instanceof Error) {
|
|
61
|
+
return {
|
|
62
|
+
name: entry.name,
|
|
63
|
+
message: entry.message,
|
|
64
|
+
stack: entry.stack,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (typeof entry === 'object' && entry !== null) {
|
|
68
|
+
if (seen.has(entry)) return '[Circular]'
|
|
69
|
+
seen.add(entry)
|
|
70
|
+
}
|
|
71
|
+
return entry
|
|
72
|
+
},
|
|
73
|
+
2,
|
|
74
|
+
)
|
|
75
|
+
} catch {
|
|
76
|
+
try {
|
|
77
|
+
return String(value)
|
|
78
|
+
} catch {
|
|
79
|
+
return '[Unserializable meta]'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
3
84
|
/**
|
|
4
85
|
* Default `Logger` implementation that routes each level to the matching
|
|
5
86
|
* `console` method:
|
|
@@ -9,41 +90,55 @@ import type { Logger } from './types'
|
|
|
9
90
|
* - `warn` → `console.warn`
|
|
10
91
|
* - `error` → `console.error`
|
|
11
92
|
*
|
|
12
|
-
* When a `meta` object is supplied
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `[Object]` / `[Array]`. On Node this produces a depth-unlimited inspect
|
|
17
|
-
* dump; browsers present the object as an interactive tree (extra options
|
|
18
|
-
* are ignored).
|
|
93
|
+
* When a `meta` object is supplied it is rendered with the strategy that
|
|
94
|
+
* actually surfaces it on the current runtime (see {@link MetaStrategy}):
|
|
95
|
+
* depth-unlimited `console.dir` on Node, circular-safe JSON on Cloudflare
|
|
96
|
+
* Workers, and an extra console argument everywhere else.
|
|
19
97
|
*
|
|
20
98
|
* This is the logger used when `debug` is enabled on any activity and no
|
|
21
99
|
* custom `logger` is supplied via `debug: { logger }`.
|
|
22
100
|
*/
|
|
23
|
-
const DIR_OPTIONS = { depth: null, colors: true } as const
|
|
24
|
-
|
|
25
101
|
export class ConsoleLogger implements Logger {
|
|
26
102
|
/** Log a debug-level message; forwards to `console.debug`. */
|
|
27
103
|
debug(message: string, meta?: Record<string, unknown>): void {
|
|
28
|
-
|
|
29
|
-
if (meta !== undefined) console.dir(meta, DIR_OPTIONS)
|
|
104
|
+
this.emit('debug', message, meta)
|
|
30
105
|
}
|
|
31
106
|
|
|
32
107
|
/** Log an info-level message; forwards to `console.info`. */
|
|
33
108
|
info(message: string, meta?: Record<string, unknown>): void {
|
|
34
|
-
|
|
35
|
-
if (meta !== undefined) console.dir(meta, DIR_OPTIONS)
|
|
109
|
+
this.emit('info', message, meta)
|
|
36
110
|
}
|
|
37
111
|
|
|
38
112
|
/** Log a warning-level message; forwards to `console.warn`. */
|
|
39
113
|
warn(message: string, meta?: Record<string, unknown>): void {
|
|
40
|
-
|
|
41
|
-
if (meta !== undefined) console.dir(meta, DIR_OPTIONS)
|
|
114
|
+
this.emit('warn', message, meta)
|
|
42
115
|
}
|
|
43
116
|
|
|
44
117
|
/** Log an error-level message; forwards to `console.error`. */
|
|
45
118
|
error(message: string, meta?: Record<string, unknown>): void {
|
|
46
|
-
|
|
47
|
-
|
|
119
|
+
this.emit('error', message, meta)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private emit(
|
|
123
|
+
level: 'debug' | 'info' | 'warn' | 'error',
|
|
124
|
+
message: string,
|
|
125
|
+
meta?: Record<string, unknown>,
|
|
126
|
+
): void {
|
|
127
|
+
if (meta === undefined) {
|
|
128
|
+
console[level](message)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
switch (resolveMetaStrategy()) {
|
|
132
|
+
case 'dir':
|
|
133
|
+
console[level](message)
|
|
134
|
+
console.dir(meta, DIR_OPTIONS)
|
|
135
|
+
break
|
|
136
|
+
case 'json':
|
|
137
|
+
console[level](`${message}\n${stringifyMetaSafely(meta)}`)
|
|
138
|
+
break
|
|
139
|
+
case 'arg':
|
|
140
|
+
console[level](message, meta)
|
|
141
|
+
break
|
|
142
|
+
}
|
|
48
143
|
}
|
|
49
144
|
}
|
package/src/logger/types.ts
CHANGED
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
export interface Logger {
|
|
5
5
|
/**
|
|
6
6
|
* Called for chunk-level diagnostic output (raw provider chunks, per-chunk output, agent-loop iteration markers).
|
|
7
|
-
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record;
|
|
7
|
+
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record; the default `ConsoleLogger` renders it with a runtime-appropriate strategy (depth-unlimited `console.dir` on Node, JSON appended to the message on Cloudflare Workers, a second `console.<level>` argument elsewhere).
|
|
8
8
|
*/
|
|
9
9
|
debug: (message: string, meta?: Record<string, unknown>) => void
|
|
10
10
|
/**
|
|
11
11
|
* Called for notable informational events (outgoing requests, tool invocations, middleware transitions).
|
|
12
|
-
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record;
|
|
12
|
+
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record; the default `ConsoleLogger` renders it with a runtime-appropriate strategy (depth-unlimited `console.dir` on Node, JSON appended to the message on Cloudflare Workers, a second `console.<level>` argument elsewhere).
|
|
13
13
|
*/
|
|
14
14
|
info: (message: string, meta?: Record<string, unknown>) => void
|
|
15
15
|
/**
|
|
16
16
|
* Called for notable warnings that don't halt execution (deprecations, recoverable anomalies).
|
|
17
|
-
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record;
|
|
17
|
+
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record; the default `ConsoleLogger` renders it with a runtime-appropriate strategy (depth-unlimited `console.dir` on Node, JSON appended to the message on Cloudflare Workers, a second `console.<level>` argument elsewhere).
|
|
18
18
|
*/
|
|
19
19
|
warn: (message: string, meta?: Record<string, unknown>) => void
|
|
20
20
|
/**
|
|
21
21
|
* Called for caught exceptions throughout the pipeline.
|
|
22
|
-
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record;
|
|
22
|
+
* @param meta Structured data forwarded to the underlying logger. Loggers like pino will preserve this as a structured record; the default `ConsoleLogger` renders it with a runtime-appropriate strategy (depth-unlimited `console.dir` on Node, JSON appended to the message on Cloudflare Workers, a second `console.<level>` argument elsewhere).
|
|
23
23
|
*/
|
|
24
24
|
error: (message: string, meta?: Record<string, unknown>) => void
|
|
25
25
|
}
|
package/src/middlewares/otel.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
ChatMiddleware,
|
|
21
21
|
ChatMiddlewareContext,
|
|
22
22
|
} from '../activities/chat/middleware/types'
|
|
23
|
+
import type { TokenUsage } from '../types'
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Scope (role) of an OTel span emitted by this middleware.
|
|
@@ -179,6 +180,59 @@ function firstNumber(...candidates: Array<unknown>): number | undefined {
|
|
|
179
180
|
return undefined
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Build the full set of `gen_ai.usage.*` span attributes from a `TokenUsage`.
|
|
185
|
+
*
|
|
186
|
+
* Beyond input/output tokens, this emits provider-reported cost, total tokens,
|
|
187
|
+
* cache and reasoning breakdowns, and duration-based billing — every field is
|
|
188
|
+
* guarded so spans stay clean when a provider doesn't report it. Cache and
|
|
189
|
+
* reasoning use the official GenAI semconv names; `gen_ai.usage.cost` and
|
|
190
|
+
* `gen_ai.usage.total_tokens` are de-facto extensions consumed by backends
|
|
191
|
+
* like PostHog (which otherwise re-derive cost from their own price tables,
|
|
192
|
+
* losing cache discounts and gateway markup). Fields with no semconv or
|
|
193
|
+
* de-facto convention (`costDetails`, `durationSeconds`) are
|
|
194
|
+
* TanStack-namespaced. Deliberately not emitted: `unitsBilled`,
|
|
195
|
+
* `providerUsageDetails`, and the per-modality token breakdowns — those are
|
|
196
|
+
* media-oriented; media-activity observability is tracked in #720.
|
|
197
|
+
*/
|
|
198
|
+
function usageAttributes(usage: TokenUsage): Record<string, AttributeValue> {
|
|
199
|
+
const attrs: Record<string, AttributeValue> = {
|
|
200
|
+
'gen_ai.usage.input_tokens': usage.promptTokens,
|
|
201
|
+
'gen_ai.usage.output_tokens': usage.completionTokens,
|
|
202
|
+
}
|
|
203
|
+
const optional: Array<[key: string, value: unknown]> = [
|
|
204
|
+
['gen_ai.usage.total_tokens', usage.totalTokens],
|
|
205
|
+
['gen_ai.usage.cost', usage.cost],
|
|
206
|
+
[
|
|
207
|
+
'gen_ai.usage.cache_read.input_tokens',
|
|
208
|
+
usage.promptTokensDetails?.cachedTokens,
|
|
209
|
+
],
|
|
210
|
+
[
|
|
211
|
+
'gen_ai.usage.cache_creation.input_tokens',
|
|
212
|
+
usage.promptTokensDetails?.cacheWriteTokens,
|
|
213
|
+
],
|
|
214
|
+
[
|
|
215
|
+
'gen_ai.usage.reasoning.output_tokens',
|
|
216
|
+
usage.completionTokensDetails?.reasoningTokens,
|
|
217
|
+
],
|
|
218
|
+
['tanstack.ai.usage.duration_seconds', usage.durationSeconds],
|
|
219
|
+
['tanstack.ai.usage.upstream_cost', usage.costDetails?.upstreamCost],
|
|
220
|
+
[
|
|
221
|
+
'tanstack.ai.usage.upstream_input_cost',
|
|
222
|
+
usage.costDetails?.upstreamInputCost,
|
|
223
|
+
],
|
|
224
|
+
[
|
|
225
|
+
'tanstack.ai.usage.upstream_output_cost',
|
|
226
|
+
usage.costDetails?.upstreamOutputCost,
|
|
227
|
+
],
|
|
228
|
+
]
|
|
229
|
+
for (const [key, value] of optional) {
|
|
230
|
+
const num = firstNumber(value)
|
|
231
|
+
if (num !== undefined) attrs[key] = num
|
|
232
|
+
}
|
|
233
|
+
return attrs
|
|
234
|
+
}
|
|
235
|
+
|
|
182
236
|
function errorMessage(err: unknown): string | undefined {
|
|
183
237
|
if (err instanceof Error) return err.message
|
|
184
238
|
if (typeof err === 'string') return err
|
|
@@ -524,10 +578,7 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
524
578
|
// `runOnUsage` when `chunk.usage` is present, and `onUsage` is the
|
|
525
579
|
// canonical place for the metric. Recording in both would double-count.
|
|
526
580
|
if (chunk.usage) {
|
|
527
|
-
span.setAttributes(
|
|
528
|
-
'gen_ai.usage.input_tokens': chunk.usage.promptTokens,
|
|
529
|
-
'gen_ai.usage.output_tokens': chunk.usage.completionTokens,
|
|
530
|
-
})
|
|
581
|
+
span.setAttributes(usageAttributes(chunk.usage))
|
|
531
582
|
}
|
|
532
583
|
|
|
533
584
|
if (captureContent && state.assistantTextBuffer.length > 0) {
|
|
@@ -584,10 +635,7 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
584
635
|
}
|
|
585
636
|
|
|
586
637
|
const span = state.currentIterationSpan ?? state.rootSpan
|
|
587
|
-
span.setAttributes(
|
|
588
|
-
'gen_ai.usage.input_tokens': usage.promptTokens,
|
|
589
|
-
'gen_ai.usage.output_tokens': usage.completionTokens,
|
|
590
|
-
})
|
|
638
|
+
span.setAttributes(usageAttributes(usage))
|
|
591
639
|
})
|
|
592
640
|
},
|
|
593
641
|
|
|
@@ -905,10 +953,7 @@ export function otelMiddleware(options: OtelMiddlewareOptions): ChatMiddleware {
|
|
|
905
953
|
}
|
|
906
954
|
|
|
907
955
|
if (info.usage) {
|
|
908
|
-
state.rootSpan.setAttributes(
|
|
909
|
-
'gen_ai.usage.input_tokens': info.usage.promptTokens,
|
|
910
|
-
'gen_ai.usage.output_tokens': info.usage.completionTokens,
|
|
911
|
-
})
|
|
956
|
+
state.rootSpan.setAttributes(usageAttributes(info.usage))
|
|
912
957
|
}
|
|
913
958
|
if (info.finishReason) {
|
|
914
959
|
state.rootSpan.setAttribute('gen_ai.response.finish_reasons', [
|
package/src/realtime/index.ts
CHANGED
|
@@ -22,9 +22,7 @@ export type * from './types'
|
|
|
22
22
|
* .handler(async () => {
|
|
23
23
|
* return realtimeToken({
|
|
24
24
|
* adapter: openaiRealtimeToken({
|
|
25
|
-
* model: 'gpt-
|
|
26
|
-
* voice: 'alloy',
|
|
27
|
-
* instructions: 'You are a helpful assistant...',
|
|
25
|
+
* model: 'gpt-realtime',
|
|
28
26
|
* }),
|
|
29
27
|
* })
|
|
30
28
|
* })
|
package/src/types.ts
CHANGED
|
@@ -51,6 +51,7 @@ export type ToolCallState =
|
|
|
51
51
|
| 'approval-requested' // Waiting for user approval
|
|
52
52
|
| 'approval-responded' // User has approved/denied
|
|
53
53
|
| 'complete' // Result is complete
|
|
54
|
+
| 'error' // Tool execution failed (terminal)
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
57
|
* Tool result states - track the lifecycle of a tool result
|
|
@@ -1656,6 +1657,12 @@ export interface VideoUrlResult {
|
|
|
1656
1657
|
url: string
|
|
1657
1658
|
/** When the URL expires, if applicable */
|
|
1658
1659
|
expiresAt?: Date
|
|
1660
|
+
/**
|
|
1661
|
+
* Usage information for the completed generation, when the adapter can report
|
|
1662
|
+
* it. For usage-based providers (e.g. fal) this carries `unitsBilled` — the
|
|
1663
|
+
* real billed quantity — so consumers can compute exact cost.
|
|
1664
|
+
*/
|
|
1665
|
+
usage?: TokenUsage
|
|
1659
1666
|
}
|
|
1660
1667
|
|
|
1661
1668
|
// ============================================================================
|