@pikku/core 0.12.33 → 0.12.35

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/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 0.12.35
2
+
3
+ ### Patch Changes
4
+
5
+ - 6bca38f: Extend `aiAgentRunner` with AI SDK-style media methods for transcription, speech, image generation, embeddings, and reranking.
6
+
7
+ Move `voiceInput` and `voiceOutput` into `@pikku/core/ai-agent`, backed by the injected `aiAgentRunner`.
8
+
9
+ Deprecate `@pikku/ai-voice` and strip its exports.
10
+
11
+ ## 0.12.34
12
+
13
+ ### Patch Changes
14
+
15
+ - 2eaa9fd: fix(workflow): seed sessionService when session already present on wire
16
+
17
+ When a parent workflow propagates its session to a child workflow via
18
+ `wire.session`, `resolveSession` skipped `setInitial` because `!wire.session`
19
+ was false, so `sessionService.freezeInitial()` returned `undefined` and
20
+ immediately overwrote the propagated session. We now seed the sessionService
21
+ with the existing `wire.session` so `freezeInitial()` returns the correct
22
+ session for `pikkuFunc` steps inside child workflows.
23
+
1
24
  ## 0.12.33
2
25
 
3
26
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@ export type { SchemaService } from './services/schema-service.js';
34
34
  export type { SessionService } from './services/user-session-service.js';
35
35
  export { NoopAuditService, createInvocationAudit, resolveAuditActorFromWire, resolveAuditConfig, } from './services/audit-service.js';
36
36
  export type { AuditActor, AuditConfig, AuditDurability, AuditEvent, AuditEventBatch, AuditLog, AuditLogWriteInput, AuditOutcome, AuditService, AuditSource, ResolvedAuditConfig, } from './services/audit-service.js';
37
- export type { AIAgentRunnerService } from './services/ai-agent-runner-service.js';
37
+ export type { AIAgentRunnerService, AIEmbedManyParams, AIEmbedManyResult, AIEmbedParams, AIEmbedResult, AIGenerateImageParams, AIGenerateImagePrompt, AIGenerateImageResult, AIGenerateSpeechParams, AIGenerateSpeechResult, AIProviderOptions, AIRerankParams, AIRerankResult, AITranscriptionParams, AITranscriptionResult, } from './services/ai-agent-runner-service.js';
38
38
  export type { AIRunStateService } from './services/ai-run-state-service.js';
39
39
  export type { AIStorageService } from './services/ai-storage-service.js';
40
40
  export type { EmailsMeta, EmailTemplateMeta, EmailTemplateLocaleMeta, EmailTemplateAssets, MetaService, } from './services/meta-service.js';
@@ -1,4 +1,5 @@
1
1
  import type { AIMessage, AIAgentStep, AIAgentToolDef, AIStreamChannel } from '../wirings/ai-agent/ai-agent.types.js';
2
+ export type AIProviderOptions = Record<string, Record<string, unknown>>;
2
3
  export type AIAgentRunnerParams = {
3
4
  model: string;
4
5
  temperature?: number;
@@ -41,9 +42,151 @@ export type AIAgentStepResult = {
41
42
  finishReason: 'stop' | 'tool-calls' | 'length' | 'error' | 'unknown';
42
43
  reasoningContent?: string;
43
44
  };
45
+ export type AITranscriptionParams = {
46
+ model: string;
47
+ audio: Uint8Array;
48
+ providerOptions?: AIProviderOptions;
49
+ maxRetries?: number;
50
+ abortSignal?: AbortSignal;
51
+ headers?: Record<string, string>;
52
+ };
53
+ export type AITranscriptionResult = {
54
+ text: string;
55
+ segments?: Array<{
56
+ text: string;
57
+ startSecond: number;
58
+ endSecond: number;
59
+ }>;
60
+ language?: string;
61
+ durationInSeconds?: number;
62
+ warnings?: unknown[];
63
+ providerMetadata?: Record<string, unknown>;
64
+ responses?: unknown[];
65
+ };
66
+ export type AIGenerateSpeechParams = {
67
+ model: string;
68
+ text: string;
69
+ voice?: string;
70
+ outputFormat?: string;
71
+ instructions?: string;
72
+ speed?: number;
73
+ language?: string;
74
+ providerOptions?: AIProviderOptions;
75
+ maxRetries?: number;
76
+ abortSignal?: AbortSignal;
77
+ headers?: Record<string, string>;
78
+ };
79
+ export type AIGenerateSpeechResult = {
80
+ audio: {
81
+ uint8Array: Uint8Array;
82
+ base64: string;
83
+ mediaType: string;
84
+ format: string;
85
+ };
86
+ warnings?: unknown[];
87
+ providerMetadata?: Record<string, unknown>;
88
+ responses?: unknown[];
89
+ };
90
+ export type AIGenerateImagePrompt = string | {
91
+ images: Array<Uint8Array | ArrayBuffer | string>;
92
+ text?: string;
93
+ mask?: Uint8Array | ArrayBuffer | string;
94
+ };
95
+ export type AIGenerateImageParams = {
96
+ model: string;
97
+ prompt: AIGenerateImagePrompt;
98
+ n?: number;
99
+ maxImagesPerCall?: number;
100
+ size?: `${number}x${number}`;
101
+ aspectRatio?: `${number}:${number}`;
102
+ seed?: number;
103
+ providerOptions?: AIProviderOptions;
104
+ maxRetries?: number;
105
+ abortSignal?: AbortSignal;
106
+ headers?: Record<string, string>;
107
+ };
108
+ export type AIGenerateImageResult = {
109
+ images: Array<{
110
+ uint8Array: Uint8Array;
111
+ base64: string;
112
+ mediaType: string;
113
+ }>;
114
+ warnings?: unknown[];
115
+ providerMetadata?: Record<string, unknown>;
116
+ responses?: unknown[];
117
+ usage?: {
118
+ inputTokens?: number;
119
+ outputTokens?: number;
120
+ totalTokens?: number;
121
+ };
122
+ };
123
+ export type AIEmbedParams = {
124
+ model: string;
125
+ value: string;
126
+ providerOptions?: AIProviderOptions;
127
+ maxRetries?: number;
128
+ abortSignal?: AbortSignal;
129
+ headers?: Record<string, string>;
130
+ };
131
+ export type AIEmbedResult = {
132
+ value: string;
133
+ embedding: number[];
134
+ usage?: {
135
+ tokens?: number;
136
+ };
137
+ warnings?: unknown[];
138
+ providerMetadata?: Record<string, unknown>;
139
+ response?: unknown;
140
+ };
141
+ export type AIEmbedManyParams = {
142
+ model: string;
143
+ values: string[];
144
+ providerOptions?: AIProviderOptions;
145
+ maxRetries?: number;
146
+ abortSignal?: AbortSignal;
147
+ headers?: Record<string, string>;
148
+ maxParallelCalls?: number;
149
+ };
150
+ export type AIEmbedManyResult = {
151
+ values: string[];
152
+ embeddings: number[][];
153
+ usage?: {
154
+ tokens?: number;
155
+ };
156
+ warnings?: unknown[];
157
+ providerMetadata?: Record<string, unknown>;
158
+ responses?: unknown[];
159
+ };
160
+ export type AIRerankParams<VALUE extends string | Record<string, unknown>> = {
161
+ model: string;
162
+ query: string;
163
+ documents: VALUE[];
164
+ topK?: number;
165
+ providerOptions?: AIProviderOptions;
166
+ maxRetries?: number;
167
+ abortSignal?: AbortSignal;
168
+ headers?: Record<string, string>;
169
+ };
170
+ export type AIRerankResult<VALUE extends string | Record<string, unknown>> = {
171
+ ranking: Array<{
172
+ index: number;
173
+ document: VALUE;
174
+ score: number;
175
+ }>;
176
+ rerankedDocuments: VALUE[];
177
+ originalDocuments: VALUE[];
178
+ providerMetadata?: Record<string, unknown>;
179
+ response?: unknown;
180
+ };
44
181
  export interface AIAgentRunnerService {
45
182
  stream(params: AIAgentRunnerParams, channel: AIStreamChannel): Promise<AIAgentStepResult>;
46
183
  run(params: AIAgentRunnerParams): Promise<AIAgentStepResult>;
184
+ transcribe?(params: AITranscriptionParams): Promise<AITranscriptionResult>;
185
+ generateSpeech?(params: AIGenerateSpeechParams): Promise<AIGenerateSpeechResult>;
186
+ generateImage?(params: AIGenerateImageParams): Promise<AIGenerateImageResult>;
187
+ embed?(params: AIEmbedParams): Promise<AIEmbedResult>;
188
+ embedMany?(params: AIEmbedManyParams): Promise<AIEmbedManyResult>;
189
+ rerank?<VALUE extends string | Record<string, unknown>>(params: AIRerankParams<VALUE>): Promise<AIRerankResult<VALUE>>;
47
190
  /** Return a new runner that uses the given API key for every LLM call.
48
191
  * Optional — runners that don't support per-key scoping leave this undefined. */
49
192
  withApiKey?(apiKey: string): AIAgentRunnerService;
@@ -30,7 +30,7 @@ export type { TriggerService } from './trigger-service.js';
30
30
  export type { GatewayService } from './gateway-service.js';
31
31
  export type { DeploymentService, DeploymentConfig, DeploymentInfo, DeploymentServiceConfig, } from './deployment-service.js';
32
32
  export type { AIStorageService } from './ai-storage-service.js';
33
- export type { AIAgentRunnerParams, AIAgentRunnerResult, AIAgentStepResult, AIAgentRunnerService, } from './ai-agent-runner-service.js';
33
+ export type { AIAgentRunnerParams, AIAgentRunnerResult, AIAgentStepResult, AIEmbedManyParams, AIEmbedManyResult, AIEmbedParams, AIEmbedResult, AIGenerateImageParams, AIGenerateImagePrompt, AIGenerateImageResult, AIGenerateSpeechParams, AIGenerateSpeechResult, AIProviderOptions, AIRerankParams, AIRerankResult, AITranscriptionParams, AITranscriptionResult, AIAgentRunnerService, } from './ai-agent-runner-service.js';
34
34
  export type { CreateRunInput, AIRunStateService, } from './ai-run-state-service.js';
35
35
  export type { CredentialStatus, CredentialMeta, } from './typed-secret-service.js';
36
36
  export type { CredentialService } from './credential-service.js';
@@ -1,6 +1,8 @@
1
1
  export { agent, agentStream, agentResume, agentApprove, } from './ai-agent-helpers.js';
2
2
  export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js';
3
3
  export { streamAIAgent, resumeAIAgent } from './ai-agent-stream.js';
4
+ export { voiceInput } from './voice-input.js';
5
+ export { voiceOutput } from './voice-output.js';
4
6
  export { type RunAIAgentParams, type StreamAIAgentOptions, ToolApprovalRequired, ToolCredentialRequired, } from './ai-agent-prepare.js';
5
7
  export { addAIAgent, approveAIAgent, getAIAgents, getAIAgentsMeta, } from './ai-agent-registry.js';
6
8
  export type { AIAgentInput, AIAgentInputAttachment, AIAgentMeta, AIAgentMemoryConfig, AIAgentStep, AIContentPart, AgentRunRow, AgentRunService, AgentRunState, AIMessage, AIStreamChannel, AIStreamEvent, AIThread, CoreAIAgent, PendingApproval, PikkuAIMiddlewareHooks, } from './ai-agent.types.js';
@@ -1,5 +1,7 @@
1
1
  export { agent, agentStream, agentResume, agentApprove, } from './ai-agent-helpers.js';
2
2
  export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js';
3
3
  export { streamAIAgent, resumeAIAgent } from './ai-agent-stream.js';
4
+ export { voiceInput } from './voice-input.js';
5
+ export { voiceOutput } from './voice-output.js';
4
6
  export { ToolApprovalRequired, ToolCredentialRequired, } from './ai-agent-prepare.js';
5
7
  export { addAIAgent, approveAIAgent, getAIAgents, getAIAgentsMeta, } from './ai-agent-registry.js';
@@ -0,0 +1,9 @@
1
+ export declare const voiceInput: (config?: {
2
+ language?: string;
3
+ model?: string;
4
+ allowedAudioHosts?: string[];
5
+ }) => import("./ai-agent.types.js").PikkuAIMiddlewareHooks<Record<string, unknown>, import("../../types/core.types.js").CoreSingletonServices<{
6
+ logLevel?: import("../../services/logger.js").LogLevel;
7
+ secrets?: {};
8
+ workflow?: import("../workflow/workflow.types.js").WorkflowServiceConfig;
9
+ }>>;
@@ -0,0 +1,113 @@
1
+ import { pikkuAIMiddleware } from '../../types/core.types.js';
2
+ function base64ToUint8Array(base64) {
3
+ const binary = atob(base64);
4
+ const bytes = new Uint8Array(binary.length);
5
+ for (let i = 0; i < binary.length; i++) {
6
+ bytes[i] = binary.charCodeAt(i);
7
+ }
8
+ return bytes;
9
+ }
10
+ const MAX_AUDIO_SIZE = 50 * 1024 * 1024;
11
+ // Portable SSRF guard: @pikku/core runs in edge runtimes (CF Workers) with no
12
+ // Node `dns`, so we cannot resolve hostnames to check for private targets.
13
+ // Reject the obvious internal literals; callers wanting stricter control pass
14
+ // an explicit `allowedAudioHosts` allowlist. (Does not defend against a public
15
+ // hostname that resolves to a private IP / DNS rebinding — out of reach here.)
16
+ function isPrivateHost(hostname) {
17
+ const host = hostname.replace(/^\[|\]$/g, '').toLowerCase();
18
+ if (host === 'localhost' || host === '0.0.0.0' || host === '::1')
19
+ return true;
20
+ if (host.startsWith('fe80:') || host.startsWith('fc') || host.startsWith('fd'))
21
+ return true;
22
+ const v4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.\d{1,3}$/);
23
+ if (v4) {
24
+ const [a, b] = [Number(v4[1]), Number(v4[2])];
25
+ if (a === 127 || a === 10 || a === 0)
26
+ return true;
27
+ if (a === 169 && b === 254)
28
+ return true; // link-local incl. cloud metadata
29
+ if (a === 172 && b >= 16 && b <= 31)
30
+ return true;
31
+ if (a === 192 && b === 168)
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ async function fetchAsUint8Array(url, allowedAudioHosts) {
37
+ const parsed = new URL(url);
38
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
39
+ throw new Error('Only HTTP(S) URLs are supported for audio');
40
+ }
41
+ if (allowedAudioHosts) {
42
+ if (!allowedAudioHosts.includes(parsed.hostname)) {
43
+ throw new Error(`Audio URL host is not allowed: ${parsed.hostname}`);
44
+ }
45
+ }
46
+ else if (isPrivateHost(parsed.hostname)) {
47
+ throw new Error(`Refusing to fetch audio from a private/internal host: ${parsed.hostname}`);
48
+ }
49
+ const response = await fetch(url);
50
+ const contentLength = response.headers.get('content-length');
51
+ if (contentLength && parseInt(contentLength, 10) > MAX_AUDIO_SIZE) {
52
+ throw new Error('Audio file exceeds maximum size');
53
+ }
54
+ const buffer = await response.arrayBuffer();
55
+ if (buffer.byteLength > MAX_AUDIO_SIZE) {
56
+ throw new Error('Audio file exceeds maximum size');
57
+ }
58
+ return new Uint8Array(buffer);
59
+ }
60
+ export const voiceInput = (config) => pikkuAIMiddleware({
61
+ modifyInput: async (services, { messages, instructions }) => {
62
+ const transcribeAudio = services.aiAgentRunner?.transcribe;
63
+ if (!transcribeAudio)
64
+ return { messages, instructions };
65
+ const last = messages[messages.length - 1];
66
+ if (!last || last.role !== 'user' || typeof last.content === 'string') {
67
+ return { messages, instructions };
68
+ }
69
+ const parts = last.content;
70
+ if (!parts)
71
+ return { messages, instructions };
72
+ const hasAudio = parts.some((p) => p.type === 'file' && !!p.mediaType?.startsWith('audio/'));
73
+ if (!hasAudio)
74
+ return { messages, instructions };
75
+ // Process parts in order, replacing each audio part with its transcription
76
+ // in place. Sequential, so no unbounded parallel downloads/transcriptions
77
+ // and original content ordering is preserved.
78
+ const updatedContent = [];
79
+ for (const p of parts) {
80
+ if (!(p.type === 'file' && p.mediaType?.startsWith('audio/'))) {
81
+ updatedContent.push(p);
82
+ continue;
83
+ }
84
+ if (!config?.model) {
85
+ throw new Error('voiceInput requires a transcription model (e.g. openai/whisper-1)');
86
+ }
87
+ const audioData = p.data
88
+ ? base64ToUint8Array(p.data)
89
+ : await fetchAsUint8Array(p.url, config.allowedAudioHosts);
90
+ const result = await transcribeAudio({
91
+ model: config.model,
92
+ audio: audioData,
93
+ ...(config.language
94
+ ? {
95
+ providerOptions: {
96
+ openai: {
97
+ language: config.language,
98
+ },
99
+ },
100
+ }
101
+ : {}),
102
+ });
103
+ updatedContent.push({ type: 'text', text: result.text });
104
+ }
105
+ return {
106
+ messages: [
107
+ ...messages.slice(0, -1),
108
+ { ...last, content: updatedContent },
109
+ ],
110
+ instructions,
111
+ };
112
+ },
113
+ });
@@ -0,0 +1,16 @@
1
+ type VoiceOutputState = {
2
+ textBuffer?: string;
3
+ };
4
+ export declare const voiceOutput: (config?: {
5
+ model?: string;
6
+ format?: string;
7
+ voice?: string;
8
+ instructions?: string;
9
+ speed?: number;
10
+ language?: string;
11
+ }) => import("./ai-agent.types.js").PikkuAIMiddlewareHooks<VoiceOutputState, import("../../types/core.types.js").CoreSingletonServices<{
12
+ logLevel?: import("../../services/logger.js").LogLevel;
13
+ secrets?: {};
14
+ workflow?: import("../workflow/workflow.types.js").WorkflowServiceConfig;
15
+ }>>;
16
+ export {};
@@ -0,0 +1,94 @@
1
+ import { pikkuAIMiddleware } from '../../types/core.types.js';
2
+ function bufferToBase64(data) {
3
+ let binary = '';
4
+ for (let i = 0; i < data.length; i++) {
5
+ binary += String.fromCharCode(data[i]);
6
+ }
7
+ return btoa(binary);
8
+ }
9
+ const SENTENCE_BOUNDARY = /[.!?]\s*$/;
10
+ function isSentenceBoundary(text) {
11
+ return SENTENCE_BOUNDARY.test(text);
12
+ }
13
+ async function synthesizeAudio(aiAgentRunner, input) {
14
+ const result = await aiAgentRunner.generateSpeech?.({
15
+ model: input.model,
16
+ text: input.text,
17
+ voice: input.voice,
18
+ outputFormat: input.format,
19
+ instructions: input.instructions,
20
+ speed: input.speed,
21
+ language: input.language,
22
+ });
23
+ if (!result) {
24
+ throw new Error('voiceOutput requires an aiAgentRunner with generateSpeech support');
25
+ }
26
+ // Label chunks with the format the provider actually returned (config.format
27
+ // is only a request), falling back to the requested format then pcm16.
28
+ return {
29
+ bytes: result.audio.uint8Array,
30
+ format: result.audio.format || input.format || 'pcm16',
31
+ };
32
+ }
33
+ export const voiceOutput = (config) => pikkuAIMiddleware({
34
+ modifyOutputStream: async (services, { event, state }) => {
35
+ const aiAgentRunner = services.aiAgentRunner;
36
+ if (!aiAgentRunner?.generateSpeech)
37
+ return event;
38
+ if (event.type === 'done') {
39
+ const remaining = state.textBuffer ?? '';
40
+ if (remaining) {
41
+ state.textBuffer = '';
42
+ if (!config?.model) {
43
+ throw new Error('voiceOutput requires a speech model (e.g. openai/tts-1)');
44
+ }
45
+ const audio = await synthesizeAudio(aiAgentRunner, {
46
+ model: config.model,
47
+ text: remaining,
48
+ voice: config?.voice,
49
+ format: config?.format,
50
+ instructions: config?.instructions,
51
+ speed: config?.speed,
52
+ language: config?.language,
53
+ });
54
+ return [
55
+ {
56
+ type: 'audio-delta',
57
+ data: bufferToBase64(audio.bytes),
58
+ format: audio.format,
59
+ },
60
+ { type: 'audio-done' },
61
+ event,
62
+ ];
63
+ }
64
+ return [{ type: 'audio-done' }, event];
65
+ }
66
+ if (event.type !== 'text-delta')
67
+ return event;
68
+ state.textBuffer = `${state.textBuffer ?? ''}${event.text}`;
69
+ if (!isSentenceBoundary(state.textBuffer))
70
+ return event;
71
+ const text = state.textBuffer;
72
+ state.textBuffer = '';
73
+ if (!config?.model) {
74
+ throw new Error('voiceOutput requires a speech model (e.g. openai/tts-1)');
75
+ }
76
+ const audio = await synthesizeAudio(aiAgentRunner, {
77
+ model: config.model,
78
+ text,
79
+ voice: config?.voice,
80
+ format: config?.format,
81
+ instructions: config?.instructions,
82
+ speed: config?.speed,
83
+ language: config?.language,
84
+ });
85
+ return [
86
+ event,
87
+ {
88
+ type: 'audio-delta',
89
+ data: bufferToBase64(audio.bytes),
90
+ format: audio.format,
91
+ },
92
+ ];
93
+ },
94
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.33",
3
+ "version": "0.12.35",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
package/src/index.ts CHANGED
@@ -142,7 +142,23 @@ export type {
142
142
  AuditSource,
143
143
  ResolvedAuditConfig,
144
144
  } from './services/audit-service.js'
145
- export type { AIAgentRunnerService } from './services/ai-agent-runner-service.js'
145
+ export type {
146
+ AIAgentRunnerService,
147
+ AIEmbedManyParams,
148
+ AIEmbedManyResult,
149
+ AIEmbedParams,
150
+ AIEmbedResult,
151
+ AIGenerateImageParams,
152
+ AIGenerateImagePrompt,
153
+ AIGenerateImageResult,
154
+ AIGenerateSpeechParams,
155
+ AIGenerateSpeechResult,
156
+ AIProviderOptions,
157
+ AIRerankParams,
158
+ AIRerankResult,
159
+ AITranscriptionParams,
160
+ AITranscriptionResult,
161
+ } from './services/ai-agent-runner-service.js'
146
162
  export type { AIRunStateService } from './services/ai-run-state-service.js'
147
163
  export type { AIStorageService } from './services/ai-storage-service.js'
148
164
  export type {
@@ -5,6 +5,8 @@ import type {
5
5
  AIStreamChannel,
6
6
  } from '../wirings/ai-agent/ai-agent.types.js'
7
7
 
8
+ export type AIProviderOptions = Record<string, Record<string, unknown>>
9
+
8
10
  export type AIAgentRunnerParams = {
9
11
  model: string
10
12
  temperature?: number
@@ -36,12 +38,169 @@ export type AIAgentStepResult = {
36
38
  reasoningContent?: string
37
39
  }
38
40
 
41
+ export type AITranscriptionParams = {
42
+ model: string
43
+ audio: Uint8Array
44
+ providerOptions?: AIProviderOptions
45
+ maxRetries?: number
46
+ abortSignal?: AbortSignal
47
+ headers?: Record<string, string>
48
+ }
49
+
50
+ export type AITranscriptionResult = {
51
+ text: string
52
+ segments?: Array<{
53
+ text: string
54
+ startSecond: number
55
+ endSecond: number
56
+ }>
57
+ language?: string
58
+ durationInSeconds?: number
59
+ warnings?: unknown[]
60
+ providerMetadata?: Record<string, unknown>
61
+ responses?: unknown[]
62
+ }
63
+
64
+ export type AIGenerateSpeechParams = {
65
+ model: string
66
+ text: string
67
+ voice?: string
68
+ outputFormat?: string
69
+ instructions?: string
70
+ speed?: number
71
+ language?: string
72
+ providerOptions?: AIProviderOptions
73
+ maxRetries?: number
74
+ abortSignal?: AbortSignal
75
+ headers?: Record<string, string>
76
+ }
77
+
78
+ export type AIGenerateSpeechResult = {
79
+ audio: {
80
+ uint8Array: Uint8Array
81
+ base64: string
82
+ mediaType: string
83
+ format: string
84
+ }
85
+ warnings?: unknown[]
86
+ providerMetadata?: Record<string, unknown>
87
+ responses?: unknown[]
88
+ }
89
+
90
+ export type AIGenerateImagePrompt =
91
+ | string
92
+ | {
93
+ images: Array<Uint8Array | ArrayBuffer | string>
94
+ text?: string
95
+ mask?: Uint8Array | ArrayBuffer | string
96
+ }
97
+
98
+ export type AIGenerateImageParams = {
99
+ model: string
100
+ prompt: AIGenerateImagePrompt
101
+ n?: number
102
+ maxImagesPerCall?: number
103
+ size?: `${number}x${number}`
104
+ aspectRatio?: `${number}:${number}`
105
+ seed?: number
106
+ providerOptions?: AIProviderOptions
107
+ maxRetries?: number
108
+ abortSignal?: AbortSignal
109
+ headers?: Record<string, string>
110
+ }
111
+
112
+ export type AIGenerateImageResult = {
113
+ images: Array<{
114
+ uint8Array: Uint8Array
115
+ base64: string
116
+ mediaType: string
117
+ }>
118
+ warnings?: unknown[]
119
+ providerMetadata?: Record<string, unknown>
120
+ responses?: unknown[]
121
+ usage?: {
122
+ inputTokens?: number
123
+ outputTokens?: number
124
+ totalTokens?: number
125
+ }
126
+ }
127
+
128
+ export type AIEmbedParams = {
129
+ model: string
130
+ value: string
131
+ providerOptions?: AIProviderOptions
132
+ maxRetries?: number
133
+ abortSignal?: AbortSignal
134
+ headers?: Record<string, string>
135
+ }
136
+
137
+ export type AIEmbedResult = {
138
+ value: string
139
+ embedding: number[]
140
+ usage?: { tokens?: number }
141
+ warnings?: unknown[]
142
+ providerMetadata?: Record<string, unknown>
143
+ response?: unknown
144
+ }
145
+
146
+ export type AIEmbedManyParams = {
147
+ model: string
148
+ values: string[]
149
+ providerOptions?: AIProviderOptions
150
+ maxRetries?: number
151
+ abortSignal?: AbortSignal
152
+ headers?: Record<string, string>
153
+ maxParallelCalls?: number
154
+ }
155
+
156
+ export type AIEmbedManyResult = {
157
+ values: string[]
158
+ embeddings: number[][]
159
+ usage?: { tokens?: number }
160
+ warnings?: unknown[]
161
+ providerMetadata?: Record<string, unknown>
162
+ responses?: unknown[]
163
+ }
164
+
165
+ export type AIRerankParams<VALUE extends string | Record<string, unknown>> = {
166
+ model: string
167
+ query: string
168
+ documents: VALUE[]
169
+ topK?: number
170
+ providerOptions?: AIProviderOptions
171
+ maxRetries?: number
172
+ abortSignal?: AbortSignal
173
+ headers?: Record<string, string>
174
+ }
175
+
176
+ export type AIRerankResult<VALUE extends string | Record<string, unknown>> = {
177
+ ranking: Array<{
178
+ index: number
179
+ document: VALUE
180
+ score: number
181
+ }>
182
+ rerankedDocuments: VALUE[]
183
+ originalDocuments: VALUE[]
184
+ providerMetadata?: Record<string, unknown>
185
+ response?: unknown
186
+ }
187
+
39
188
  export interface AIAgentRunnerService {
40
189
  stream(
41
190
  params: AIAgentRunnerParams,
42
191
  channel: AIStreamChannel
43
192
  ): Promise<AIAgentStepResult>
44
193
  run(params: AIAgentRunnerParams): Promise<AIAgentStepResult>
194
+ transcribe?(params: AITranscriptionParams): Promise<AITranscriptionResult>
195
+ generateSpeech?(
196
+ params: AIGenerateSpeechParams
197
+ ): Promise<AIGenerateSpeechResult>
198
+ generateImage?(params: AIGenerateImageParams): Promise<AIGenerateImageResult>
199
+ embed?(params: AIEmbedParams): Promise<AIEmbedResult>
200
+ embedMany?(params: AIEmbedManyParams): Promise<AIEmbedManyResult>
201
+ rerank?<VALUE extends string | Record<string, unknown>>(
202
+ params: AIRerankParams<VALUE>
203
+ ): Promise<AIRerankResult<VALUE>>
45
204
  /** Return a new runner that uses the given API key for every LLM call.
46
205
  * Optional — runners that don't support per-key scoping leave this undefined. */
47
206
  withApiKey?(apiKey: string): AIAgentRunnerService