@outputai/core 0.15.1-next.4a437bf.0 → 0.15.1-next.8ef04e2.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.
Files changed (62) hide show
  1. package/package.json +58 -6
  2. package/src/helpers/fetch.js +21 -9
  3. package/src/helpers/path.js +36 -0
  4. package/src/helpers/redact.js +2 -1
  5. package/src/http/config.js +10 -0
  6. package/src/http/cost.d.ts +20 -0
  7. package/src/http/cost.js +41 -0
  8. package/src/http/fetch/events.js +10 -0
  9. package/src/http/fetch/index.d.ts +26 -0
  10. package/src/http/fetch/index.js +78 -0
  11. package/src/http/fetch/logger.d.ts +10 -0
  12. package/src/http/fetch/logger.js +56 -0
  13. package/src/http/index.d.ts +27 -0
  14. package/src/http/index.js +9 -0
  15. package/src/http/ky/index.d.ts +25 -0
  16. package/src/http/ky/index.js +10 -0
  17. package/src/http/request_tag.js +30 -0
  18. package/src/llm/agent.js +141 -0
  19. package/src/llm/ai_provider.js +131 -0
  20. package/src/llm/ai_sdk_options.js +126 -0
  21. package/src/llm/consts.js +6 -0
  22. package/src/llm/deprecated_provider_aliases.js +8 -0
  23. package/src/llm/generate.js +88 -0
  24. package/src/llm/index.d.ts +631 -0
  25. package/src/llm/index.js +5 -0
  26. package/src/llm/prompt/content.js +70 -0
  27. package/src/llm/prompt/interpolations.js +58 -0
  28. package/src/llm/prompt/loader.js +67 -0
  29. package/src/llm/prompt/markup/attributes.js +52 -0
  30. package/src/llm/prompt/markup/nodes.js +71 -0
  31. package/src/llm/prompt/markup/tokenizer.js +3 -0
  32. package/src/llm/prompt/markup/tokens.js +26 -0
  33. package/src/llm/prompt/validations.js +129 -0
  34. package/src/llm/utils/cost.js +150 -0
  35. package/src/llm/utils/error_handler.js +53 -0
  36. package/src/llm/utils/file.js +42 -0
  37. package/src/llm/utils/google_vertex_auth.js +31 -0
  38. package/src/llm/utils/grounding.js +49 -0
  39. package/src/llm/utils/image.js +37 -0
  40. package/src/llm/utils/legacy_cost_attribute.js +75 -0
  41. package/src/llm/utils/metering.js +126 -0
  42. package/src/llm/utils/models.js +15 -0
  43. package/src/llm/utils/models_pricing.js +93 -0
  44. package/src/llm/utils/models_pricing_snapshot.json +4166 -0
  45. package/src/llm/utils/skills.js +74 -0
  46. package/src/llm/utils/sources.js +73 -0
  47. package/src/llm/utils/stream.js +75 -0
  48. package/src/llm/utils/tools.js +48 -0
  49. package/src/llm/utils/usage.js +156 -0
  50. package/src/llm/utils/usage_tools.js +23 -0
  51. package/src/llm/utils/wrap.js +277 -0
  52. package/src/llm/validations.js +234 -0
  53. package/src/sdk/helpers/path.d.ts +2 -0
  54. package/src/sdk/helpers/path.js +1 -0
  55. package/src/tracing/trace_attribute.d.ts +0 -17
  56. package/src/tracing/trace_attribute.js +1 -29
  57. package/src/worker/configs.js +23 -4
  58. package/src/worker/index.js +16 -47
  59. package/src/worker/interceptors/activity.js +0 -1
  60. package/src/worker/interceptors/workflow.js +0 -1
  61. package/src/worker/telemetry.js +122 -2
  62. package/src/worker/worker_options.js +43 -0
@@ -0,0 +1,277 @@
1
+ import { extractSources } from './sources.js';
2
+ import { serializeImagesFromResponse } from './image.js';
3
+ import { addEventActionWithContext, EventAction } from '#tracing';
4
+ import { mapAiError } from './error_handler.js';
5
+ import { isPromise } from 'node:util/types';
6
+ import { FatalError } from '#errors';
7
+ import { Logger } from '#runtime_logger';
8
+ import { Metering } from './metering.js';
9
+ import { randomBytes } from 'node:crypto';
10
+
11
+ /** Creates a proxy of the AI SDK response and attach virtual getters to it based on an object map */
12
+ const createResponseProxy = ( { response, properties } ) => new Proxy( response, {
13
+ get( target, prop, receiver ) {
14
+ return Object.hasOwn( properties, prop ) ? properties[prop] : Reflect.get( target, prop, receiver );
15
+ }
16
+ } );
17
+
18
+ /** Generate a trace id, starts the tracing and return the id */
19
+ const startTrace = ( { name, prompt } ) => {
20
+ const traceId = `${name}-${Date.now()}-${randomBytes( 4 ).toString( 'hex' )}`;
21
+ addEventActionWithContext( EventAction.START, { kind: 'llm', id: traceId, name, details: { prompt } } );
22
+ return traceId;
23
+ };
24
+
25
+ /** Handle AI SDK errors: map them, add an error trace entry and return the new error */
26
+ const handleAiSdkError = ( { traceId, error: originalError } ) => {
27
+ const error = mapAiError( originalError );
28
+ addEventActionWithContext( EventAction.ERROR, { id: traceId, details: error } );
29
+ return error;
30
+ };
31
+
32
+ /**
33
+ * Handle a failure of our own response handling: wrap it, add an error trace entry and return it.
34
+ * Fatal on purpose - such a failure is deterministic, so a retry pays for the model call again and
35
+ * fails the same way.
36
+ */
37
+ const handleResponseError = ( { traceId, error: cause } ) => {
38
+ const error = new FatalError( 'AI SDK response handling failed.', { cause } );
39
+ addEventActionWithContext( EventAction.ERROR, { id: traceId, details: error } );
40
+ return error;
41
+ };
42
+
43
+ /** Invokes an async function, wrapped in a try/catch, return { result, error } */
44
+ const inlineTryAsync = async fn => {
45
+ try {
46
+ return { result: await fn(), error: null };
47
+ } catch ( error ) {
48
+ return { result: null, error };
49
+ }
50
+ };
51
+
52
+ /**
53
+ * Runs a completing AI SDK text call (`generateText`, `generateTextWithStreaming`,
54
+ * `Agent.generate`, `Agent.generateWithStreaming`): starts the LLM trace, runs `fn`, ends the trace
55
+ * and returns the response proxied with `result` (`text`), `cost` and merged `sources`.
56
+ *
57
+ * `fn` receives `onStepEndHook` to wire into the SDK `onStepEnd`, so the usage of every completed
58
+ * step is collected as the call progresses and still lands when `fn` throws after the model ran.
59
+ * The response `fn` returns is billed directly, and its aggregate usage takes precedence over the
60
+ * collected steps. Trace output keeps raw `response.usage`; normalized usage and cost are trace
61
+ * attributes.
62
+ *
63
+ * Reading the billed response is guarded: a throw from `fn` is mapped as an SDK error, while a
64
+ * failure of our own handling becomes a `FatalError` recorded on the trace. The proxy is built
65
+ * before the trace ends, so a call that fails there never leaves an `end` entry next to an `error`.
66
+ *
67
+ * @param {object} args
68
+ * @param {string} args.name - Trace event name
69
+ * @param {object} args.prompt - Loaded prompt (`config.provider` / `config.model` used for cost)
70
+ * @param {( wiring: { onStepEndHook: Function } ) => Promise<object>} args.fn - AI SDK call; wire
71
+ * `onStepEndHook` into the call `onStepEnd` and return the SDK response
72
+ * @returns {Promise<object>} Proxied SDK response
73
+ */
74
+ export const wrapTextGeneration = async ( { name, prompt, fn } ) => {
75
+ const traceId = startTrace( { name, prompt } );
76
+
77
+ const metering = new Metering( { traceId, prompt } );
78
+
79
+ const onStepEndHook = metering.recordStep;
80
+
81
+ const { result: response, error } = await inlineTryAsync( () => fn( { onStepEndHook } ) );
82
+
83
+ await metering.bill( response );
84
+
85
+ if ( error ) {
86
+ throw handleAiSdkError( { traceId, error } );
87
+ }
88
+
89
+ try {
90
+ const { text: result, finalStep, usage } = response;
91
+ const providerMetadata = finalStep?.providerMetadata;
92
+ const sources = extractSources( response );
93
+
94
+ // Create proxy first, as this could theoretically fail
95
+ const responseProxy = createResponseProxy( { response, properties: { cost: metering.attributes.cost, sources, result } } );
96
+ addEventActionWithContext( EventAction.END, { id: traceId, details: { result, usage, providerMetadata, sources } } );
97
+
98
+ return responseProxy;
99
+ } catch ( error ) {
100
+ throw handleResponseError( { traceId, error } );
101
+ }
102
+ };
103
+
104
+ /**
105
+ * Runs `generateImage`: starts the LLM trace, meters the returned usage, ends the trace and returns
106
+ * the response proxied with `result` (the first `image`) and `cost`.
107
+ *
108
+ * Image calls expose no step lifecycle, so usage is read from the response. With no
109
+ * step loop and no output parsing, a throw from `fn` happens before any usage exists, so it is
110
+ * mapped and recorded without metering. Reading the billed response is guarded the same way
111
+ * `wrapTextGeneration` guards it: a failure there is a `FatalError` recorded after billing.
112
+ *
113
+ * @param {object} args
114
+ * @param {string} args.name - Trace event name
115
+ * @param {object} args.prompt - Loaded prompt (`config.provider` / `config.model` used for cost)
116
+ * @param {() => Promise<object>} args.fn - AI SDK call; must return the SDK response
117
+ * @returns {Promise<object>} Proxied SDK response
118
+ */
119
+ export const wrapImageGeneration = async ( { name, prompt, fn } ) => {
120
+ const traceId = startTrace( { name, prompt } );
121
+
122
+ const metering = new Metering( { traceId, prompt } );
123
+
124
+ const { result: response, error } = await inlineTryAsync( fn );
125
+
126
+ if ( error ) {
127
+ throw handleAiSdkError( { traceId, error } );
128
+ }
129
+
130
+ await metering.bill( response );
131
+
132
+ try {
133
+ const { usage, image: result, providerMetadata } = response;
134
+ const serializeImages = serializeImagesFromResponse( response );
135
+
136
+ // Create proxy first, as this could theoretically fail
137
+ const responseProxy = createResponseProxy( { response, properties: { cost: metering.attributes.cost, result } } );
138
+
139
+ addEventActionWithContext( EventAction.END, { id: traceId, details: { result: serializeImages, usage, providerMetadata } } );
140
+ return responseProxy;
141
+ } catch ( error ) {
142
+ throw handleResponseError( { traceId, error } );
143
+ }
144
+ };
145
+
146
+ /** Awaits a consumer callback, logging and swallowing its failures: the hooks are fire and forget */
147
+ const invokeCallback = async ( cb, args, cbName ) => {
148
+ if ( typeof cb === 'function' ) {
149
+ try {
150
+ await cb( ...args );
151
+ } catch ( e ) {
152
+ Logger.error( `Stream ${cbName}() callback failed`, { namespace: 'LLM', error: e?.message || String( e ) } );
153
+ }
154
+ }
155
+ };
156
+
157
+ /**
158
+ * Starts an LLM trace around a live AI SDK stream (`streamText`, `Agent.stream`).
159
+ *
160
+ * `fn` receives `onEndHook(response, callback)` and `onErrorHook(event, callback)` to wire into the
161
+ * SDK `onEnd` / `onError`, plus `onStepEndHook` and `onAbortHook` for `onStepEnd` / `onAbort`. Both
162
+ * terminal hooks bill, end or record the trace, then invoke `callback`; a `callback` that is not a
163
+ * function is skipped, and callback failures are logged, never rethrown.
164
+ *
165
+ * Neither hook throws, because the SDK invokes them through `notify`, which swallows callback
166
+ * failures and discards their return: a failure while reading the response would vanish, so it is
167
+ * logged and recorded as a trace error here instead. An `onError`
168
+ * event carrying no `Error` is normalized to `Streaming failed.` with the original value as `cause`,
169
+ * so the trace always closes and the consumer callback always runs.
170
+ *
171
+ * Metering is fed by `onStepEndHook`, which collects step usage so the tokens survive the exits
172
+ * that report no response: `NoOutputGeneratedError` skips `onEnd`, and an abort fires neither
173
+ * terminal hook. `onAbortHook` bills that case, and the SDK awaits it before enqueueing the abort
174
+ * part, so billing from there always lands before the consumer observes the cancellation.
175
+ *
176
+ * A throw or rejected Promise from `fn` (stream creation / Agent setup) is mapped and recorded on
177
+ * the trace; a non-Promise return (the `streamText` stream) is returned as is, with a rejection of
178
+ * its `output` promise recorded without being swallowed from the consumer. An abort on
179
+ * `abortSignal` records its reason as a trace error, and the listener is removed when the stream
180
+ * ends, reports an error, or fails during setup. An abort the SDK raises on its own timeout leaves
181
+ * `abortSignal` untouched, so `onAbort` records that one instead.
182
+ *
183
+ * @param {object} args
184
+ * @param {string} args.name - Trace event name
185
+ * @param {object} args.prompt - Loaded prompt (`config.provider` / `config.model` used for cost)
186
+ * @param {AbortSignal} [args.abortSignal] - Optional signal used to trace stream cancellation
187
+ * @param {( wiring: { onEndHook: Function, onErrorHook: Function, onStepEndHook: Function,
188
+ * onAbortHook: Function } ) => object | Promise<object>} args.fn - Wire the hooks into the call
189
+ * and return the SDK stream, or a Promise of that stream (`Agent.stream`)
190
+ * @returns {object | Promise<object>} Value returned by `fn`; a rejected Promise is remapped
191
+ */
192
+ export const wrapStream = ( { name, prompt, abortSignal, fn } ) => {
193
+ const traceId = startTrace( { name, prompt } );
194
+ const metering = new Metering( { traceId, prompt } );
195
+
196
+ const handleAbort = () => {
197
+ const error = abortSignal?.reason instanceof Error ? abortSignal.reason : new Error( 'Streaming aborted.', { cause: abortSignal?.reason } );
198
+ handleAiSdkError( { traceId, error } );
199
+ };
200
+
201
+ if ( abortSignal ) {
202
+ if ( abortSignal.aborted ) {
203
+ handleAbort();
204
+ } else {
205
+ abortSignal.addEventListener( 'abort', handleAbort, { once: true } );
206
+ }
207
+ }
208
+ const removeAbortListener = () => abortSignal?.removeEventListener( 'abort', handleAbort );
209
+
210
+ const onStepEndHook = metering.recordStep;
211
+
212
+ const onAbortHook = async () => {
213
+ removeAbortListener();
214
+
215
+ await metering.bill();
216
+ // abortion was caused by timeout and not signal
217
+ if ( !abortSignal?.aborted ) {
218
+ handleAiSdkError( { traceId, error: new Error( 'Streaming timed out.' ) } );
219
+ }
220
+ };
221
+
222
+ const onEndHook = async ( response, callback ) => {
223
+ removeAbortListener();
224
+
225
+ await metering.bill( response );
226
+
227
+ try {
228
+ const { text: result, finalStep, usage } = response;
229
+ const providerMetadata = finalStep?.providerMetadata;
230
+ const sources = extractSources( response );
231
+
232
+ const proxyResponse = createResponseProxy( { response, properties: { cost: metering.attributes.cost, sources, result } } );
233
+ addEventActionWithContext( EventAction.END, { id: traceId, details: { result, usage, providerMetadata, sources } } );
234
+
235
+ await invokeCallback( callback, [ proxyResponse ], 'onEnd' );
236
+ } catch ( error ) {
237
+ Logger.error( 'AI SDK response handling failed', { namespace: 'LLM', error: error?.message || String( error ) } );
238
+ handleResponseError( { traceId, error } );
239
+ }
240
+ };
241
+
242
+ const onErrorHook = async ( event, callback ) => {
243
+ removeAbortListener();
244
+ await metering.bill();
245
+
246
+ const error = event?.error instanceof Error ? event.error : new Error( 'Streaming failed.', { cause: event?.error } );
247
+ const mappedError = handleAiSdkError( { traceId, error } );
248
+ await invokeCallback( callback, [ mappedError ], 'onError' );
249
+ };
250
+
251
+ const handleStreamError = error => {
252
+ removeAbortListener();
253
+ return handleAiSdkError( { traceId, error } );
254
+ };
255
+
256
+ const addStreamOutputErrorHandler = stream => {
257
+ stream.output?.catch( error => handleAiSdkError( { traceId, error } ) );
258
+ return stream;
259
+ };
260
+
261
+ const createStream = () => {
262
+ try {
263
+ return fn( { onEndHook, onErrorHook, onStepEndHook, onAbortHook } );
264
+ } catch ( error ) {
265
+ throw handleStreamError( error );
266
+ }
267
+ };
268
+
269
+ const stream = createStream();
270
+
271
+ /** Streams can be a promise or not, for both cases add the handler to catch .output errors, and if it is a promise add a .catch handler to observe errors there as well */
272
+ return isPromise( stream ) ?
273
+ stream.then( addStreamOutputErrorHandler ).catch( e => {
274
+ throw handleStreamError( e );
275
+ } ) :
276
+ addStreamOutputErrorHandler( stream );
277
+ };
@@ -0,0 +1,234 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { z } from 'zod';
3
+ import { ValidationError } from '#errors';
4
+ import { Role } from './consts.js';
5
+ import { promptSchema } from './prompt/validations.js';
6
+
7
+ const rejectPromptOwnedCallArg = name => z.unknown().optional().refine(
8
+ value => value === undefined,
9
+ `${name} must be set in the prompt file, not as a call argument`
10
+ );
11
+
12
+ // Listed so .strict() callers get the prompt-file message instead of an unrecognized-key error.
13
+ // @TODO Do not drop this later.
14
+ const promptOwnedCallArgRejection = {
15
+ maxSteps: rejectPromptOwnedCallArg( 'maxSteps' ),
16
+ skills: rejectPromptOwnedCallArg( 'skills' )
17
+ };
18
+
19
+ const functionSchema = z.custom( value => typeof value === 'function', 'Expected function' );
20
+
21
+ const variablesSchema = z.record( z.string(), z.unknown() );
22
+
23
+ const toolChoiceSchema = z.union( [
24
+ z.enum( [ 'auto', 'none', 'required' ] ),
25
+ z.object( {
26
+ type: z.literal( 'tool' ),
27
+ toolName: z.string().min( 1 )
28
+ } ).strict()
29
+ ] );
30
+
31
+ const stopWhenSchema = z.union( [
32
+ functionSchema,
33
+ z.array( functionSchema ).min( 1 )
34
+ ] );
35
+
36
+ const isObject = value => value !== null && typeof value === 'object' && !Array.isArray( value );
37
+
38
+ const opaqueObjectSchema = z.custom( isObject, 'Expected object' );
39
+
40
+ const outputSchema = opaqueObjectSchema;
41
+
42
+ const toolsSchema = z.record( z.string().min( 1 ), opaqueObjectSchema );
43
+
44
+ const messageStoreSchema = z.custom(
45
+ value => value !== null &&
46
+ typeof value === 'object' &&
47
+ typeof value.getMessages === 'function' &&
48
+ typeof value.addMessages === 'function',
49
+ 'Expected an object with getMessages and addMessages functions'
50
+ );
51
+
52
+ const modelMessageSchema = z.object( {
53
+ role: z.enum( [ ...Object.values( Role ), 'tool' ] ),
54
+ content: z.union( [ z.string(), z.array( z.unknown() ) ] )
55
+ } ).loose();
56
+
57
+ const promptFileSchema = z.string().min( 1 );
58
+
59
+ const promptInputSchema = z.unknown().transform( ( value, ctx ) => {
60
+ const result = ( isObject( value ) ? promptSchema : promptFileSchema ).safeParse( value );
61
+ if ( !result.success ) {
62
+ for ( const issue of result.error.issues ) {
63
+ ctx.addIssue( issue );
64
+ }
65
+ return value;
66
+ }
67
+ return result.data;
68
+ } );
69
+
70
+ const promptCallFields = {
71
+ prompt: promptInputSchema,
72
+ promptDir: z.string().min( 1 ).optional(),
73
+ variables: variablesSchema.optional()
74
+ };
75
+
76
+ const textRuntimeCallFields = {
77
+ abortSignal: z.instanceof( AbortSignal ).optional(),
78
+ output: outputSchema.optional(),
79
+ stopWhen: stopWhenSchema.optional(),
80
+ toolChoice: toolChoiceSchema.optional(),
81
+ tools: toolsSchema.optional()
82
+ };
83
+
84
+ const generateTextCallFields = {
85
+ ...promptCallFields,
86
+ ...promptOwnedCallArgRejection,
87
+ ...textRuntimeCallFields
88
+ };
89
+
90
+ const toPromptArgs = ( { prompt, maxSteps, skills, ...rest } ) => ( {
91
+ ...( typeof prompt === 'string' ? { promptFile: prompt } : { promptObject: prompt } ),
92
+ ...rest
93
+ } );
94
+
95
+ const rejectPromptObjectFileArgs = ( args, ctx ) => {
96
+ if ( !isObject( args.prompt ) ) {
97
+ return;
98
+ }
99
+
100
+ for ( const field of [ 'promptDir', 'variables' ] ) {
101
+ if ( args[field] !== undefined ) {
102
+ ctx.addIssue( {
103
+ code: 'custom',
104
+ path: [ field ],
105
+ message: `${field} cannot be used when prompt is an object`
106
+ } );
107
+ }
108
+ }
109
+ };
110
+
111
+ const generateTextArgsSchema = z.object( generateTextCallFields )
112
+ .strict()
113
+ .superRefine( rejectPromptObjectFileArgs )
114
+ .transform( toPromptArgs );
115
+
116
+ const generateTextWithStreamingArgsSchema = z.object( {
117
+ ...generateTextCallFields,
118
+ onChunk: functionSchema.optional()
119
+ } ).strict().superRefine( rejectPromptObjectFileArgs ).transform( toPromptArgs );
120
+
121
+ const streamTextArgsSchema = z.object( {
122
+ ...generateTextCallFields,
123
+ onChunk: functionSchema.optional(),
124
+ onError: functionSchema.optional(),
125
+ onEnd: functionSchema.optional()
126
+ } ).strict().superRefine( rejectPromptObjectFileArgs ).transform( toPromptArgs );
127
+
128
+ const agentCallFields = {
129
+ ...promptCallFields,
130
+ ...promptOwnedCallArgRejection,
131
+ messageStore: messageStoreSchema.optional(),
132
+ output: outputSchema.optional(),
133
+ stopWhen: stopWhenSchema.optional(),
134
+ tools: toolsSchema.optional()
135
+ };
136
+
137
+ const agentMethodCallFields = {
138
+ abortSignal: z.instanceof( AbortSignal ).optional(),
139
+ messages: z.array( modelMessageSchema ).default( [] ),
140
+ toolChoice: toolChoiceSchema.optional(),
141
+ ...promptOwnedCallArgRejection
142
+ };
143
+
144
+ const agentArgsSchema = z.object( agentCallFields )
145
+ .strict()
146
+ .superRefine( rejectPromptObjectFileArgs )
147
+ .transform( toPromptArgs );
148
+
149
+ const agentGenerateArgsSchema = z.object( agentMethodCallFields ).strict();
150
+
151
+ const agentGenerateWithStreamingArgsSchema = z.object( {
152
+ ...agentMethodCallFields,
153
+ onChunk: functionSchema.optional()
154
+ } ).strict();
155
+
156
+ const agentStreamArgsSchema = z.object( {
157
+ ...agentMethodCallFields,
158
+ onChunk: functionSchema.optional(),
159
+ onError: functionSchema.optional(),
160
+ onEnd: functionSchema.optional()
161
+ } ).strict();
162
+
163
+ const base64StringSchema = z.string()
164
+ .min( 1 )
165
+ .regex(
166
+ /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}(?:==)?|[A-Za-z0-9+/]{3}=?)?$/,
167
+ 'Image strings must be raw base64 data.'
168
+ );
169
+
170
+ const imageDataSchema = z.union( [
171
+ z.instanceof( Buffer ),
172
+ z.instanceof( Uint8Array ),
173
+ z.instanceof( ArrayBuffer ),
174
+ base64StringSchema
175
+ ] );
176
+
177
+ const imageInputSchema = z.union( [
178
+ imageDataSchema,
179
+ z.object( {
180
+ data: imageDataSchema,
181
+ mediaType: z.string().min( 1 ).optional()
182
+ } ).strict()
183
+ ] );
184
+
185
+ const generateImageCallFields = {
186
+ abortSignal: z.instanceof( AbortSignal ).optional(),
187
+ images: z.array( imageInputSchema ).min( 1 ).optional(),
188
+ mask: imageInputSchema.optional(),
189
+ ...promptCallFields
190
+ };
191
+
192
+ const generateImageArgsSchema = z.object( generateImageCallFields ).strict().superRefine( ( args, ctx ) => {
193
+ rejectPromptObjectFileArgs( args, ctx );
194
+
195
+ if ( args.mask && !args.images ) {
196
+ ctx.addIssue( {
197
+ code: 'custom',
198
+ path: [ 'mask' ],
199
+ message: 'mask requires images.'
200
+ } );
201
+ }
202
+ } ).transform( toPromptArgs );
203
+
204
+ const parseSchema = ( schema, input, errorPrefix ) => {
205
+ const result = schema.safeParse( input );
206
+ if ( !result.success ) {
207
+ throw new ValidationError( `${errorPrefix}: ${z.prettifyError( result.error )}` );
208
+ }
209
+ return result.data;
210
+ };
211
+
212
+ export const parseGenerateTextArgs = args =>
213
+ parseSchema( generateTextArgsSchema, args, 'Invalid generateText() arguments' );
214
+
215
+ export const parseGenerateTextWithStreamingArgs = args =>
216
+ parseSchema( generateTextWithStreamingArgsSchema, args, 'Invalid generateTextWithStreaming() arguments' );
217
+
218
+ export const parseStreamTextArgs = args =>
219
+ parseSchema( streamTextArgsSchema, args, 'Invalid streamText() arguments' );
220
+
221
+ export const parseGenerateImageArgs = args =>
222
+ parseSchema( generateImageArgsSchema, args, 'Invalid generateImage() arguments' );
223
+
224
+ export const parseAgentArgs = args =>
225
+ parseSchema( agentArgsSchema, args, 'Invalid Agent() arguments' );
226
+
227
+ export const parseAgentGenerateArgs = args =>
228
+ parseSchema( agentGenerateArgsSchema, args ?? {}, 'Invalid Agent.generate() arguments' );
229
+
230
+ export const parseAgentGenerateWithStreamingArgs = args =>
231
+ parseSchema( agentGenerateWithStreamingArgsSchema, args ?? {}, 'Invalid Agent.generateWithStreaming() arguments' );
232
+
233
+ export const parseAgentStreamArgs = args =>
234
+ parseSchema( agentStreamArgsSchema, args ?? {}, 'Invalid Agent.stream() arguments' );
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * Tools to interact with filesystem paths
3
+ *
4
+ * @deprecated Internal helper with no public replacement, kept only for `@outputai/evals`. It will be removed when evals folds into `@outputai/core`.
3
5
  */
4
6
  export declare const Path: {
5
7
  /**
@@ -17,6 +17,7 @@ const defaultIgnorePaths = [
17
17
  'webpack/bootstrap'
18
18
  ];
19
19
 
20
+ /** @deprecated Internal helper with no public replacement, kept only for `@outputai/evals`. It will be removed when evals folds into `@outputai/core`. */
20
21
  export const Path = {
21
22
  resolveInvocationDir: ( additionalIgnorePaths = [] ) => {
22
23
  const stack = new Error().stack;
@@ -4,22 +4,5 @@ export declare namespace Attribute {
4
4
  constructor( type: string );
5
5
  }
6
6
 
7
- export class HTTPRequestCount extends BaseAttribute {
8
- static TYPE: 'http:request:count';
9
- type: typeof HTTPRequestCount.TYPE;
10
- url: string;
11
- requestId: string;
12
- constructor( url: string, requestId: string );
13
- }
14
-
15
- export class HTTPRequestCost extends BaseAttribute {
16
- static TYPE: 'http:request:cost';
17
- type: typeof HTTPRequestCost.TYPE;
18
- url: string;
19
- requestId: string;
20
- total: number;
21
- constructor( url: string, requestId: string, total: number );
22
- }
23
-
24
7
  export type Instance = BaseAttribute;
25
8
  }
@@ -9,37 +9,9 @@ export class BaseAttribute {
9
9
  }
10
10
  }
11
11
 
12
- class HTTPRequestCount extends BaseAttribute {
13
- static TYPE = 'http:request:count';
14
- url;
15
- requestId;
16
-
17
- constructor( url, requestId ) {
18
- super( HTTPRequestCount.TYPE );
19
- this.url = url;
20
- this.requestId = requestId;
21
- }
22
- }
23
-
24
- class HTTPRequestCost extends BaseAttribute {
25
- static TYPE = 'http:request:cost';
26
- url;
27
- requestId;
28
- total = 0;
29
-
30
- constructor( url, requestId, total ) {
31
- super( HTTPRequestCost.TYPE );
32
- this.url = url;
33
- this.requestId = requestId;
34
- this.total = total;
35
- }
36
- }
37
-
38
12
  /**
39
13
  * Types of ADD_ATTR attributes
40
14
  */
41
15
  export const Attribute = {
42
- BaseAttribute,
43
- HTTPRequestCost,
44
- HTTPRequestCount
16
+ BaseAttribute
45
17
  };
@@ -22,13 +22,31 @@ const envVarSchema = z.object( {
22
22
  // Worker concurrency — tune these via env vars to adjust for your workload.
23
23
  // Each step (API, LLM, etc.) call is one activity. Lower this to reduce memory pressure.
24
24
  TEMPORAL_MAX_CONCURRENT_ACTIVITY_TASK_EXECUTIONS: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().default( 40 ) ),
25
- // Workflows are lightweight state machines — this can be high.
26
- TEMPORAL_MAX_CONCURRENT_WORKFLOW_TASK_EXECUTIONS: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().default( 200 ) ),
27
- // LRU cache for sticky workflow execution. Lower values free memory faster after surges.
28
- TEMPORAL_MAX_CACHED_WORKFLOWS: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().default( 1000 ) ),
25
+ /*
26
+ Workflow tasks that may run at once. These are short state transitions, so this is rarely the
27
+ throughput limit - activity concurrency above is. Left unset so @temporalio/worker applies its
28
+ own default of 40; the startup 'Worker sizing' log reports it as workflowTaskSlots.
29
+ https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#maxconcurrentworkflowtaskexecutions
30
+ */
31
+ TEMPORAL_MAX_CONCURRENT_WORKFLOW_TASK_EXECUTIONS: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().optional() ),
32
+ /*
33
+ LRU cache for sticky workflow execution, holding each workflow's state until it completes or is
34
+ evicted. Left unset so @temporalio/worker sizes it from the isolate's heap limit, which a flat
35
+ value here cannot track. That formula assumes roughly 600 cached workflows per GB, so set this
36
+ where payloads are heavier - the startup 'Worker sizing' log reports what it resolved to.
37
+ https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#maxcachedworkflows
38
+ */
39
+ TEMPORAL_MAX_CACHED_WORKFLOWS: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().optional() ),
29
40
  // How aggressively the worker pulls tasks from Temporal.
30
41
  TEMPORAL_MAX_CONCURRENT_ACTIVITY_TASK_POLLS: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().default( 5 ) ),
31
42
  TEMPORAL_MAX_CONCURRENT_WORKFLOW_TASK_POLLS: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().default( 5 ) ),
43
+ /*
44
+ Threads running workflow sandboxes. Each is a V8 isolate and NODE_OPTIONS heap caps apply per
45
+ isolate, so this multiplies the heap the process can commit - the startup 'Worker sizing'
46
+ log reports the total. Left unset so @temporalio/worker picks: 1 with reuseV8Context, 2 without.
47
+ https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#workflowthreadpoolsize
48
+ */
49
+ TEMPORAL_WORKFLOW_THREAD_POOL_SIZE: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().optional() ),
32
50
  // JSON-encoded Temporal Worker tuner options.
33
51
  TEMPORAL_WORKER_TUNER: workerTunerEnvSchema,
34
52
  // Activity configs
@@ -64,6 +82,7 @@ export const maxConcurrentWorkflowTaskExecutions = envVars.TEMPORAL_MAX_CONCURRE
64
82
  export const maxCachedWorkflows = envVars.TEMPORAL_MAX_CACHED_WORKFLOWS;
65
83
  export const maxConcurrentActivityTaskPolls = envVars.TEMPORAL_MAX_CONCURRENT_ACTIVITY_TASK_POLLS;
66
84
  export const maxConcurrentWorkflowTaskPolls = envVars.TEMPORAL_MAX_CONCURRENT_WORKFLOW_TASK_POLLS;
85
+ export const workflowThreadPoolSize = envVars.TEMPORAL_WORKFLOW_THREAD_POOL_SIZE;
67
86
  export const workerTuner = envVars.TEMPORAL_WORKER_TUNER;
68
87
  export const namespace = envVars.TEMPORAL_NAMESPACE;
69
88
  export const taskQueue = envVars.OUTPUT_CATALOG_ID;