@zhivex-ai/gateway 0.3.1-next.0 → 0.4.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/index.js CHANGED
@@ -1,13 +1,96 @@
1
- import { generateObject, generateText, streamObject, streamText } from "@zhivex-ai/core";
2
- import { executeRoutedOperation } from "./attempts.js";
3
- import { gatewayMessagesToModelMessages, stripImagesForUnsupportedModel } from "./compat.js";
4
- import { createDefaultRetryPolicy, createDefaultRouterPolicy } from "./policies.js";
5
- import { createInMemoryGatewayRuntimeStore, gatewayTargetKey } from "./runtime-state.js";
1
+ import { createAgent, generateObject, runAgent, generateText, streamAgent, streamObject, streamText } from "@zhivex-ai/core";
2
+ import { createRouteDecision, gatewayMessagesToModelMessages, stripImagesForUnsupportedModel } from "./compat.js";
6
3
  import { GatewayError } from "./types.js";
7
- export * from "./policies.js";
8
- export * from "./runtime-state.js";
9
- export * from "./types.js";
4
+ const scoreTarget = (mode, intent, target, config) => {
5
+ const model = target.modelId.toLowerCase();
6
+ const localBoost = target.provider === "ollama" ? -2 : 0;
7
+ const qualityBoost = model.includes("pro") || model.includes("claude") ? 2 : 0;
8
+ const speedBoost = model.includes("flash") || model.includes("lite") ? 2 : 0;
9
+ const reasoningBoost = model.includes("pro") || model.includes("claude") ? 2 : 0;
10
+ const catalogCost = config.modelCatalog?.find(target.provider, target.modelId)?.costPer1kTokens;
11
+ const costPenalty = config.providerCostsPer1kTokens?.[target.provider] ?? catalogCost ?? 0;
12
+ const latencyPenalty = (config.latencyBiasMs?.[target.provider] ?? 0) / 100;
13
+ if (mode === "speed") {
14
+ return speedBoost + localBoost - latencyPenalty;
15
+ }
16
+ if (mode === "quality") {
17
+ return qualityBoost + (intent === "reasoning" ? reasoningBoost : 0) - costPenalty;
18
+ }
19
+ return speedBoost + qualityBoost + localBoost + (intent === "reasoning" ? 1 : 0) - costPenalty - latencyPenalty;
20
+ };
21
+ const orderTargets = (mode, intent, primary, fallbacks, config) => [primary, ...fallbacks]
22
+ .filter((target, index, list) => list.findIndex((candidate) => candidate.provider === target.provider && candidate.modelId === target.modelId) === index)
23
+ .sort((left, right) => scoreTarget(mode, intent, right, config) - scoreTarget(mode, intent, left, config));
24
+ const supportsRequiredCapabilities = (adapter, target, requiredCapabilities) => {
25
+ if (!requiredCapabilities) {
26
+ return true;
27
+ }
28
+ const capabilities = adapter.languageModel(target.modelId).capabilities;
29
+ return Object.entries(requiredCapabilities).every(([key, required]) => required !== true || capabilities[key] === true);
30
+ };
31
+ const agentTierRank = (tier) => tier === "tier-a" ? 3 : tier === "tier-b" ? 2 : tier === "tier-c" ? 1 : 0;
32
+ const supportsRequiredAgentCapabilities = (adapter, target, requiredAgentCapabilities) => {
33
+ if (!requiredAgentCapabilities) {
34
+ return true;
35
+ }
36
+ const capabilities = adapter.languageModel(target.modelId).capabilities.agentCapabilities;
37
+ if (!capabilities) {
38
+ return false;
39
+ }
40
+ return Object.entries(requiredAgentCapabilities).every(([key, value]) => {
41
+ if (value == null) {
42
+ return true;
43
+ }
44
+ if (key === "supportTier") {
45
+ return agentTierRank(capabilities.supportTier) >= agentTierRank(value);
46
+ }
47
+ return value !== true || capabilities[key] === true;
48
+ });
49
+ };
50
+ const withinCostBudget = (config, request, target) => {
51
+ if (request.maxCostPer1kTokens == null) {
52
+ return true;
53
+ }
54
+ const configuredCost = config.providerCostsPer1kTokens?.[target.provider];
55
+ const catalogCost = config.modelCatalog?.find(target.provider, target.modelId)?.costPer1kTokens;
56
+ const effectiveCost = configuredCost ?? catalogCost;
57
+ if (effectiveCost == null) {
58
+ return true;
59
+ }
60
+ return effectiveCost <= request.maxCostPer1kTokens;
61
+ };
10
62
  const estimateTokens = (text) => Math.max(1, Math.ceil(text.trim().length / 4));
63
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
64
+ const withTimeout = async (promise, timeoutMs) => new Promise((resolve, reject) => {
65
+ const timer = setTimeout(() => reject(new GatewayError(`Provider timed out after ${timeoutMs}ms.`, true)), timeoutMs);
66
+ promise
67
+ .then((value) => {
68
+ clearTimeout(timer);
69
+ resolve(value);
70
+ })
71
+ .catch((error) => {
72
+ clearTimeout(timer);
73
+ reject(error);
74
+ });
75
+ });
76
+ const normalizeError = (error) => {
77
+ if (error instanceof GatewayError) {
78
+ return error;
79
+ }
80
+ if (error instanceof Error) {
81
+ const message = error.message.toLowerCase();
82
+ if (message.includes("timed out") || message.includes("429") || message.includes("rate")) {
83
+ return new GatewayError(error.message, true);
84
+ }
85
+ if (message.includes("connect") || message.includes("econnrefused") || message.includes("503")) {
86
+ return new GatewayError(error.message, true);
87
+ }
88
+ return new GatewayError(error.message, false);
89
+ }
90
+ return new GatewayError("Unknown gateway error.", false);
91
+ };
92
+ const getAttemptTimeoutMs = (config, provider) => config.attemptTimeoutsMs?.[provider] ?? config.attemptTimeoutMs ?? 20_000;
93
+ const retryBackoffMs = (config, retry) => (config.retryBackoffMs ?? 200) * (retry + 1);
11
94
  const normalizeUsage = (usage, inputText, outputText) => {
12
95
  const inputTokens = usage?.inputTokens ?? estimateTokens(inputText);
13
96
  const outputTokens = usage?.outputTokens ?? estimateTokens(outputText);
@@ -19,17 +102,14 @@ const normalizeUsage = (usage, inputText, outputText) => {
19
102
  estimated: usage?.inputTokens == null || usage?.outputTokens == null || usage?.totalTokens == null
20
103
  };
21
104
  };
105
+ const requestMessages = (request) => gatewayMessagesToModelMessages(stripImagesForUnsupportedModel(request.messages, request.primary.provider, request.primary.modelId), request.systemPrompt);
22
106
  const getInputText = (request) => `${request.systemPrompt ?? ""}\n${request.messages.map((message) => message.content).join("\n")}`.trim();
23
- const dedupeTargets = (primary, fallbacks = []) => [primary, ...fallbacks].filter((target, index, list) => list.findIndex((candidate) => candidate.provider === target.provider && candidate.modelId === target.modelId) === index);
24
107
  const buildRequiredCapabilities = (request, extra = {}) => ({
25
108
  ...(request.requiredCapabilities ?? {}),
26
109
  ...(request.tools ? { tools: true } : {}),
27
110
  ...(request.reasoning ? { reasoning: true } : {}),
28
- ...(request.stepType === "grounded" ? { webSearch: true } : {}),
29
111
  ...extra
30
112
  });
31
- const getTaskIntent = (request) => request.taskIntent ??
32
- (request.stepType === "plan" ? "reasoning" : request.stepType === "act" ? "tool-heavy" : "chat");
33
113
  const createTextOptions = (adapter, target, request) => ({
34
114
  model: adapter.languageModel(target.modelId),
35
115
  messages: gatewayMessagesToModelMessages(stripImagesForUnsupportedModel(request.messages, target.provider, target.modelId), request.systemPrompt),
@@ -43,176 +123,308 @@ const createTextOptions = (adapter, target, request) => ({
43
123
  providerOptions: request.providerOptions,
44
124
  abortSignal: request.abortSignal
45
125
  });
46
- const enrichTextResult = (request, target, attempts, routeDecision, startedAt, result, currentTime = Date.now()) => ({
126
+ const enrichTextResult = (request, target, attempts, routeDecision, startedAt, result) => ({
47
127
  ...result,
48
128
  providerUsed: target.provider,
49
129
  modelUsed: target.modelId,
50
- latencyMs: currentTime - startedAt,
130
+ latencyMs: Date.now() - startedAt,
51
131
  attempts,
52
132
  usage: normalizeUsage(result.usage, getInputText(request), result.text),
53
133
  routeDecision
54
134
  });
55
- const enrichObjectResult = (request, target, attempts, routeDecision, startedAt, result, currentTime = Date.now()) => ({
135
+ const enrichObjectResult = (request, target, attempts, routeDecision, startedAt, result) => ({
56
136
  ...result,
57
137
  providerUsed: target.provider,
58
138
  modelUsed: target.modelId,
59
- latencyMs: currentTime - startedAt,
139
+ latencyMs: Date.now() - startedAt,
60
140
  attempts,
61
141
  usage: normalizeUsage(result.usage, getInputText(request), result.text),
62
142
  routeDecision
63
143
  });
64
- const getAttemptTimeoutMs = (config, provider) => config.attemptTimeoutsMs?.[provider] ?? config.attemptTimeoutMs ?? 20_000;
65
- const withFirstEventReplay = (streamResult, firstEvent, iterator) => {
66
- const eventStream = (async function* () {
67
- if (!firstEvent.done) {
68
- yield firstEvent.value;
69
- }
70
- while (true) {
71
- const next = await iterator.next();
72
- if (next.done) {
73
- return;
74
- }
75
- yield next.value;
76
- }
77
- })();
144
+ const createAgentMessages = (request, target) => request.messages
145
+ ? gatewayMessagesToModelMessages(stripImagesForUnsupportedModel(request.messages, target.provider, target.modelId), undefined)
146
+ : undefined;
147
+ const createAgentRunInput = (request, target) => {
148
+ const source = request.prompt !== undefined
149
+ ? { prompt: request.prompt }
150
+ : request.messages
151
+ ? { messages: createAgentMessages(request, target) }
152
+ : {};
78
153
  return {
79
- ...streamResult,
80
- eventStream
154
+ ...source,
155
+ system: request.system,
156
+ state: request.state,
157
+ approvals: request.approvals,
158
+ handoff: request.handoff,
159
+ tools: request.tools,
160
+ toolChoice: request.toolChoice,
161
+ toolExecution: request.toolExecution,
162
+ maxSteps: request.maxSteps,
163
+ temperature: request.temperature,
164
+ maxTokens: request.maxTokens,
165
+ reasoning: request.reasoning,
166
+ providerOptions: request.providerOptions,
167
+ abortSignal: request.abortSignal
81
168
  };
82
169
  };
170
+ const enrichAgentResult = (target, attempts, routeDecision, startedAt, result) => ({
171
+ ...result,
172
+ providerUsed: target.provider,
173
+ modelUsed: target.modelId,
174
+ latencyMs: Date.now() - startedAt,
175
+ attempts,
176
+ routeDecision,
177
+ state: {
178
+ ...result.state,
179
+ routeDecision
180
+ }
181
+ });
83
182
  export const createGateway = (config) => {
84
- const now = config.now ?? Date.now;
85
- const runtimeStore = config.runtimeStore ?? createInMemoryGatewayRuntimeStore();
86
- const routerPolicy = config.routerPolicy ?? createDefaultRouterPolicy();
87
- const retryPolicy = config.retryPolicy ?? createDefaultRetryPolicy({ maxRetries: config.maxRetries, backoffMs: config.retryBackoffMs });
88
- const emitEvent = async (event) => {
89
- await config.onEvent?.(event);
183
+ const getAdapter = (provider) => {
184
+ const adapter = config.adapters[provider];
185
+ if (!adapter) {
186
+ throw new GatewayError(`No adapter registered for provider "${provider}".`, false);
187
+ }
188
+ return adapter;
90
189
  };
91
- const getAdapter = (provider) => config.adapters[provider];
92
- const planRoute = async (request, extraRequiredCapabilities = {}) => {
93
- const normalizedRequest = {
94
- ...request,
95
- taskIntent: getTaskIntent(request)
190
+ const routeDecisionFor = (request) => {
191
+ const mode = request.routingMode ?? "balanced";
192
+ const intent = request.taskIntent ?? "chat";
193
+ const orderedTargets = orderTargets(mode, intent, request.primary, request.fallbacks ?? [], config);
194
+ return {
195
+ mode,
196
+ intent,
197
+ orderedTargets,
198
+ routeDecision: createRouteDecision(mode, intent, orderedTargets)
96
199
  };
97
- const targets = dedupeTargets(request.primary, request.fallbacks);
98
- const runtimeStates = new Map(await Promise.all(targets.map(async (target) => [gatewayTargetKey(target), await runtimeStore.get(gatewayTargetKey(target))])));
99
- const routeDecision = await routerPolicy.plan({
100
- request: normalizedRequest,
101
- targets,
102
- requiredCapabilities: buildRequiredCapabilities(normalizedRequest, extraRequiredCapabilities),
103
- adapters: config.adapters,
104
- runtimeStates,
105
- modelCatalog: config.modelCatalog,
106
- providerCostsPer1kTokens: config.providerCostsPer1kTokens,
107
- latencyBiasMs: config.latencyBiasMs,
108
- now: now()
109
- });
110
- await emitEvent({
111
- type: "route-selected",
112
- request: normalizedRequest,
113
- timestamp: now(),
114
- decision: routeDecision
115
- });
116
- return routeDecision;
200
+ };
201
+ const selectAgentTarget = async (request) => {
202
+ const mode = request.routingMode ?? "balanced";
203
+ const intent = request.taskIntent ?? "tool-heavy";
204
+ const orderedTargets = orderTargets(mode, intent, request.primary, request.fallbacks ?? [], config);
205
+ const routeDecision = createRouteDecision(mode, intent, orderedTargets);
206
+ const attempts = [];
207
+ for (const target of orderedTargets) {
208
+ const adapter = getAdapter(target.provider);
209
+ if (!supportsRequiredCapabilities(adapter, target, buildRequiredCapabilities(request))) {
210
+ attempts.push({
211
+ provider: target.provider,
212
+ modelId: target.modelId,
213
+ ok: false,
214
+ latencyMs: 0,
215
+ errorMessage: "Skipped because model capabilities do not satisfy the request."
216
+ });
217
+ continue;
218
+ }
219
+ if (!supportsRequiredAgentCapabilities(adapter, target, request.requiredAgentCapabilities)) {
220
+ attempts.push({
221
+ provider: target.provider,
222
+ modelId: target.modelId,
223
+ ok: false,
224
+ latencyMs: 0,
225
+ errorMessage: "Skipped because agent capabilities do not satisfy the request."
226
+ });
227
+ continue;
228
+ }
229
+ if (!withinCostBudget(config, request, target)) {
230
+ attempts.push({
231
+ provider: target.provider,
232
+ modelId: target.modelId,
233
+ ok: false,
234
+ latencyMs: 0,
235
+ errorMessage: "Skipped because provider cost exceeds the configured budget."
236
+ });
237
+ continue;
238
+ }
239
+ await config.onAgentRoute?.({
240
+ provider: target.provider,
241
+ modelId: target.modelId,
242
+ routeDecision
243
+ });
244
+ return {
245
+ target,
246
+ attempts,
247
+ routeDecision,
248
+ startedAt: Date.now()
249
+ };
250
+ }
251
+ throw new GatewayError(attempts.at(-1)?.errorMessage ?? "No gateway agent target satisfied the request.", false);
117
252
  };
118
253
  const runGenerate = async (request, operation, extraRequiredCapabilities = {}) => {
119
- const startedAt = now();
120
- const routeDecision = await planRoute(request, extraRequiredCapabilities);
121
- const executed = await executeRoutedOperation({
122
- request,
123
- routeDecision,
124
- retryPolicy,
125
- runtimeStore,
126
- getAttemptTimeoutMs: (target) => getAttemptTimeoutMs(config, target.provider),
127
- onAttempt: config.onAttempt,
128
- onEvent: config.onEvent,
129
- now,
130
- operation: async (target) => {
131
- const adapter = getAdapter(target.provider);
132
- if (!adapter) {
133
- throw new GatewayError(`No adapter registered for provider "${target.provider}".`, false);
254
+ const attempts = [];
255
+ const startedAt = Date.now();
256
+ const { orderedTargets, routeDecision } = routeDecisionFor(request);
257
+ const maxRetries = Math.max(0, config.maxRetries ?? 2);
258
+ const requiredCapabilities = buildRequiredCapabilities(request, extraRequiredCapabilities);
259
+ for (const [targetIndex, target] of orderedTargets.entries()) {
260
+ const adapter = getAdapter(target.provider);
261
+ if (!supportsRequiredCapabilities(adapter, target, requiredCapabilities)) {
262
+ attempts.push({
263
+ provider: target.provider,
264
+ modelId: target.modelId,
265
+ ok: false,
266
+ latencyMs: 0,
267
+ errorMessage: "Skipped because model capabilities do not satisfy the request."
268
+ });
269
+ continue;
270
+ }
271
+ if (!withinCostBudget(config, request, target)) {
272
+ attempts.push({
273
+ provider: target.provider,
274
+ modelId: target.modelId,
275
+ ok: false,
276
+ latencyMs: 0,
277
+ errorMessage: "Skipped because provider cost exceeds the configured budget."
278
+ });
279
+ continue;
280
+ }
281
+ for (let retry = 0; retry <= maxRetries; retry += 1) {
282
+ const attemptStartedAt = Date.now();
283
+ try {
284
+ await config.onAttempt?.({
285
+ provider: target.provider,
286
+ modelId: target.modelId,
287
+ ok: true,
288
+ latencyMs: 0,
289
+ retry,
290
+ targetRank: targetIndex
291
+ });
292
+ const result = await withTimeout(operation(adapter, target), getAttemptTimeoutMs(config, target.provider));
293
+ attempts.push({
294
+ provider: target.provider,
295
+ modelId: target.modelId,
296
+ ok: true,
297
+ latencyMs: Date.now() - attemptStartedAt
298
+ });
299
+ return {
300
+ attempts,
301
+ target,
302
+ startedAt,
303
+ routeDecision,
304
+ result
305
+ };
306
+ }
307
+ catch (error) {
308
+ const normalized = normalizeError(error);
309
+ attempts.push({
310
+ provider: target.provider,
311
+ modelId: target.modelId,
312
+ ok: false,
313
+ latencyMs: Date.now() - attemptStartedAt,
314
+ errorMessage: normalized.message
315
+ });
316
+ await config.onAttempt?.({
317
+ provider: target.provider,
318
+ modelId: target.modelId,
319
+ ok: false,
320
+ latencyMs: Date.now() - attemptStartedAt,
321
+ errorMessage: normalized.message,
322
+ retry,
323
+ targetRank: targetIndex
324
+ });
325
+ if (retry < maxRetries && normalized.retryable) {
326
+ await sleep(retryBackoffMs(config, retry));
327
+ continue;
328
+ }
329
+ break;
134
330
  }
135
- return operation(adapter, target);
136
331
  }
137
- });
138
- return {
139
- ...executed,
140
- startedAt,
141
- routeDecision
142
- };
332
+ }
333
+ const finalError = attempts.at(-1)?.errorMessage ?? "All gateway attempts failed.";
334
+ throw new GatewayError(finalError, false);
143
335
  };
144
336
  const runStream = async (request, operation, extraRequiredCapabilities = {}) => {
145
- const startedAt = now();
146
- const routeDecision = await planRoute(request, { streaming: true, ...extraRequiredCapabilities });
147
- const executed = await executeRoutedOperation({
148
- request,
149
- routeDecision,
150
- retryPolicy,
151
- runtimeStore,
152
- getAttemptTimeoutMs: (target) => getAttemptTimeoutMs(config, target.provider),
153
- onAttempt: config.onAttempt,
154
- onEvent: config.onEvent,
155
- now,
156
- operation: async (target) => {
157
- const adapter = getAdapter(target.provider);
158
- if (!adapter) {
159
- throw new GatewayError(`No adapter registered for provider "${target.provider}".`, false);
160
- }
161
- const streamResult = operation(adapter, target);
337
+ const attempts = [];
338
+ const startedAt = Date.now();
339
+ const { orderedTargets, routeDecision } = routeDecisionFor(request);
340
+ const maxRetries = Math.max(0, config.maxRetries ?? 2);
341
+ const requiredCapabilities = buildRequiredCapabilities(request, { streaming: true, ...extraRequiredCapabilities });
342
+ for (const [targetIndex, target] of orderedTargets.entries()) {
343
+ const adapter = getAdapter(target.provider);
344
+ if (!supportsRequiredCapabilities(adapter, target, requiredCapabilities)) {
345
+ attempts.push({
346
+ provider: target.provider,
347
+ modelId: target.modelId,
348
+ ok: false,
349
+ latencyMs: 0,
350
+ errorMessage: "Skipped because model capabilities do not satisfy the request."
351
+ });
352
+ continue;
353
+ }
354
+ if (!withinCostBudget(config, request, target)) {
355
+ attempts.push({
356
+ provider: target.provider,
357
+ modelId: target.modelId,
358
+ ok: false,
359
+ latencyMs: 0,
360
+ errorMessage: "Skipped because provider cost exceeds the configured budget."
361
+ });
362
+ continue;
363
+ }
364
+ for (let retry = 0; retry <= maxRetries; retry += 1) {
365
+ const attemptStartedAt = Date.now();
162
366
  try {
367
+ await config.onAttempt?.({
368
+ provider: target.provider,
369
+ modelId: target.modelId,
370
+ ok: true,
371
+ latencyMs: 0,
372
+ retry,
373
+ targetRank: targetIndex
374
+ });
375
+ const streamResult = operation(adapter, target);
163
376
  const iterator = streamResult.eventStream[Symbol.asyncIterator]();
164
- const first = await iterator.next();
377
+ const first = await withTimeout(iterator.next(), getAttemptTimeoutMs(config, target.provider));
165
378
  if (!first.done && first.value.type === "error") {
166
- await streamResult.collect().catch(() => undefined);
167
379
  throw first.value.error;
168
380
  }
169
- return withFirstEventReplay(streamResult, first, iterator);
381
+ attempts.push({
382
+ provider: target.provider,
383
+ modelId: target.modelId,
384
+ ok: true,
385
+ latencyMs: Date.now() - attemptStartedAt
386
+ });
387
+ return {
388
+ attempts,
389
+ target,
390
+ startedAt,
391
+ routeDecision,
392
+ streamResult
393
+ };
170
394
  }
171
395
  catch (error) {
172
- await streamResult.collect().catch(() => undefined);
173
- throw error;
396
+ const normalized = normalizeError(error);
397
+ attempts.push({
398
+ provider: target.provider,
399
+ modelId: target.modelId,
400
+ ok: false,
401
+ latencyMs: Date.now() - attemptStartedAt,
402
+ errorMessage: normalized.message
403
+ });
404
+ await config.onAttempt?.({
405
+ provider: target.provider,
406
+ modelId: target.modelId,
407
+ ok: false,
408
+ latencyMs: Date.now() - attemptStartedAt,
409
+ errorMessage: normalized.message,
410
+ retry,
411
+ targetRank: targetIndex
412
+ });
413
+ if (retry < maxRetries && normalized.retryable) {
414
+ await sleep(retryBackoffMs(config, retry));
415
+ continue;
416
+ }
417
+ break;
174
418
  }
175
419
  }
176
- });
177
- return {
178
- ...executed,
179
- startedAt,
180
- routeDecision
181
- };
420
+ }
421
+ const finalError = attempts.at(-1)?.errorMessage ?? "All gateway attempts failed.";
422
+ throw new GatewayError(finalError, false);
182
423
  };
183
424
  return {
184
- async resolveModel(request) {
185
- const routeDecision = await planRoute(request);
186
- const target = routeDecision.orderedTargets[0];
187
- if (!target) {
188
- throw new GatewayError("No available gateway targets matched the request.", false);
189
- }
190
- const adapter = getAdapter(target.provider);
191
- if (!adapter) {
192
- throw new GatewayError(`No adapter registered for provider "${target.provider}".`, false);
193
- }
194
- if (request.stepType === "grounded") {
195
- const groundedModel = adapter.groundedLanguageModel?.(target.modelId);
196
- if (!groundedModel) {
197
- throw new GatewayError(`Provider "${target.provider}" does not expose grounded models.`, false);
198
- }
199
- return {
200
- target,
201
- routeDecision,
202
- model: groundedModel,
203
- kind: "grounded"
204
- };
205
- }
206
- return {
207
- target,
208
- routeDecision,
209
- model: adapter.languageModel(target.modelId),
210
- kind: "language"
211
- };
212
- },
213
425
  async generate(request) {
214
426
  const routed = await runGenerate(request, (adapter, target) => generateText(createTextOptions(adapter, target, request)));
215
- return enrichTextResult(request, routed.target, routed.attempts, routed.routeDecision, routed.startedAt, routed.result, now());
427
+ return enrichTextResult(request, routed.target, routed.attempts, routed.routeDecision, routed.startedAt, routed.result);
216
428
  },
217
429
  streamText(request) {
218
430
  const selected = runStream(request, (adapter, target) => streamText(createTextOptions(adapter, target, request)));
@@ -220,21 +432,21 @@ export const createGateway = (config) => {
220
432
  const ensureRelay = async () => {
221
433
  if (!relayPromise) {
222
434
  relayPromise = selected.then((routed) => ({
223
- collect: async () => enrichTextResult(request, routed.target, routed.attempts, routed.routeDecision, routed.startedAt, await routed.result.collect(), now())
435
+ collect: async () => enrichTextResult(request, routed.target, routed.attempts, routed.routeDecision, routed.startedAt, await routed.streamResult.collect())
224
436
  }));
225
437
  }
226
438
  return relayPromise;
227
439
  };
228
440
  return {
229
441
  eventStream: (async function* () {
230
- const { result } = await selected;
231
- for await (const event of result.eventStream) {
442
+ const { streamResult } = await selected;
443
+ for await (const event of streamResult.eventStream) {
232
444
  yield event;
233
445
  }
234
446
  })(),
235
447
  textStream: (async function* () {
236
- const { result } = await selected;
237
- for await (const chunk of result.textStream) {
448
+ const { streamResult } = await selected;
449
+ for await (const chunk of streamResult.textStream) {
238
450
  yield chunk;
239
451
  }
240
452
  })(),
@@ -248,8 +460,8 @@ export const createGateway = (config) => {
248
460
  mode: request.mode,
249
461
  schemaName: request.schemaName,
250
462
  schemaDescription: request.schemaDescription
251
- }), { structuredOutput: request.mode === "native" ? true : undefined });
252
- return enrichObjectResult(request, routed.target, routed.attempts, routed.routeDecision, routed.startedAt, routed.result, now());
463
+ }));
464
+ return enrichObjectResult(request, routed.target, routed.attempts, routed.routeDecision, routed.startedAt, routed.result);
253
465
  },
254
466
  streamObject(request) {
255
467
  const selected = runStream(request, (adapter, target) => streamObject({
@@ -258,21 +470,21 @@ export const createGateway = (config) => {
258
470
  mode: request.mode,
259
471
  schemaName: request.schemaName,
260
472
  schemaDescription: request.schemaDescription
261
- }), { structuredOutput: request.mode === "native" ? true : undefined });
473
+ }));
262
474
  let relayPromise;
263
475
  const ensureRelay = async () => {
264
476
  if (!relayPromise) {
265
477
  relayPromise = selected.then((routed) => ({
266
- streamResult: routed.result,
267
- collect: async () => enrichObjectResult(request, routed.target, routed.attempts, routed.routeDecision, routed.startedAt, await routed.result.collect(), now())
478
+ streamResult: routed.streamResult,
479
+ collect: async () => enrichObjectResult(request, routed.target, routed.attempts, routed.routeDecision, routed.startedAt, await routed.streamResult.collect())
268
480
  }));
269
481
  }
270
482
  return relayPromise;
271
483
  };
272
484
  return {
273
485
  eventStream: (async function* () {
274
- const { result } = await selected;
275
- for await (const event of result.eventStream) {
486
+ const { streamResult } = await selected;
487
+ for await (const event of streamResult.eventStream) {
276
488
  yield event;
277
489
  }
278
490
  })(),
@@ -290,6 +502,82 @@ export const createGateway = (config) => {
290
502
  })(),
291
503
  collect: async () => (await ensureRelay()).collect()
292
504
  };
505
+ },
506
+ async runAgent(request) {
507
+ const selection = await selectAgentTarget(request);
508
+ const adapter = getAdapter(selection.target.provider);
509
+ const model = adapter.languageModel(selection.target.modelId);
510
+ const agent = createAgent({
511
+ id: request.agentId,
512
+ model,
513
+ instructions: request.instructions,
514
+ tools: request.tools,
515
+ maxSteps: request.maxSteps,
516
+ temperature: request.temperature,
517
+ maxTokens: request.maxTokens,
518
+ reasoning: request.reasoning,
519
+ toolExecution: request.toolExecution,
520
+ providerOptions: request.providerOptions,
521
+ metadata: request.metadata,
522
+ store: request.store,
523
+ memory: request.memory,
524
+ onTelemetryEvent: request.onTelemetryEvent
525
+ });
526
+ const result = await runAgent(agent, {
527
+ ...createAgentRunInput(request, selection.target)
528
+ });
529
+ return enrichAgentResult(selection.target, selection.attempts, selection.routeDecision, selection.startedAt, result);
530
+ },
531
+ streamAgent(request) {
532
+ const selection = selectAgentTarget(request);
533
+ let relayPromise;
534
+ const ensureRelay = async () => {
535
+ if (!relayPromise) {
536
+ relayPromise = selection.then(async (selected) => {
537
+ const adapter = getAdapter(selected.target.provider);
538
+ const model = adapter.languageModel(selected.target.modelId);
539
+ const agent = createAgent({
540
+ id: request.agentId,
541
+ model,
542
+ instructions: request.instructions,
543
+ tools: request.tools,
544
+ maxSteps: request.maxSteps,
545
+ temperature: request.temperature,
546
+ maxTokens: request.maxTokens,
547
+ reasoning: request.reasoning,
548
+ toolExecution: request.toolExecution,
549
+ providerOptions: request.providerOptions,
550
+ metadata: request.metadata,
551
+ store: request.store,
552
+ memory: request.memory,
553
+ onTelemetryEvent: request.onTelemetryEvent
554
+ });
555
+ const streamResult = streamAgent(agent, {
556
+ ...createAgentRunInput(request, selected.target)
557
+ });
558
+ return {
559
+ streamResult,
560
+ collect: async () => enrichAgentResult(selected.target, selected.attempts, selected.routeDecision, selected.startedAt, await streamResult.collect())
561
+ };
562
+ });
563
+ }
564
+ return relayPromise;
565
+ };
566
+ return {
567
+ eventStream: (async function* () {
568
+ const relay = await ensureRelay();
569
+ for await (const event of relay.streamResult.eventStream) {
570
+ yield event;
571
+ }
572
+ })(),
573
+ textStream: (async function* () {
574
+ const relay = await ensureRelay();
575
+ for await (const chunk of relay.streamResult.textStream) {
576
+ yield chunk;
577
+ }
578
+ })(),
579
+ collect: async () => (await ensureRelay()).collect()
580
+ };
293
581
  }
294
582
  };
295
583
  };