@the-open-engine/zeroshot 6.8.3 → 6.9.1

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 (37) hide show
  1. package/lib/agent-cli-provider/adapters/gateway.d.ts.map +1 -1
  2. package/lib/agent-cli-provider/adapters/gateway.js +9 -2
  3. package/lib/agent-cli-provider/adapters/gateway.js.map +1 -1
  4. package/lib/agent-cli-provider/gateway-client.d.ts +9 -2
  5. package/lib/agent-cli-provider/gateway-client.d.ts.map +1 -1
  6. package/lib/agent-cli-provider/gateway-client.js +117 -3
  7. package/lib/agent-cli-provider/gateway-client.js.map +1 -1
  8. package/lib/agent-cli-provider/gateway-runner.js +5 -0
  9. package/lib/agent-cli-provider/gateway-runner.js.map +1 -1
  10. package/lib/agent-cli-provider/gateway-tools.d.ts.map +1 -1
  11. package/lib/agent-cli-provider/gateway-tools.js +30 -0
  12. package/lib/agent-cli-provider/gateway-tools.js.map +1 -1
  13. package/lib/agent-cli-provider/index.d.ts +1 -1
  14. package/lib/agent-cli-provider/index.d.ts.map +1 -1
  15. package/lib/agent-cli-provider/index.js.map +1 -1
  16. package/lib/agent-cli-provider/provider-registry.d.ts +2 -2
  17. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  18. package/lib/agent-cli-provider/provider-registry.js +10 -2
  19. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  20. package/lib/agent-cli-provider/single-agent-runtime.js +6 -0
  21. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  22. package/lib/agent-cli-provider/types.d.ts +5 -0
  23. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  24. package/lib/agent-cli-provider/types.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/agent/agent-lifecycle.js +45 -0
  27. package/src/agent/agent-task-executor.js +20 -2
  28. package/src/agent/output-extraction.js +40 -0
  29. package/src/agent-cli-provider/adapters/gateway.ts +9 -2
  30. package/src/agent-cli-provider/gateway-client.ts +135 -4
  31. package/src/agent-cli-provider/gateway-runner.ts +5 -0
  32. package/src/agent-cli-provider/gateway-tools.ts +40 -0
  33. package/src/agent-cli-provider/index.ts +1 -0
  34. package/src/agent-cli-provider/provider-registry.ts +10 -2
  35. package/src/agent-cli-provider/single-agent-runtime.ts +6 -0
  36. package/src/agent-cli-provider/types.ts +5 -0
  37. package/src/preflight.js +3 -2
@@ -1,4 +1,5 @@
1
1
  import { getArray, getRecord, getString, isRecord, unknownToMessage } from './json';
2
+ import type { GatewayProtocol } from './types';
2
3
 
3
4
  export interface GatewayChatToolDefinition {
4
5
  readonly type: 'function';
@@ -18,6 +19,7 @@ export interface GatewayToolCall {
18
19
  export interface GatewayChatMessage {
19
20
  readonly role: 'system' | 'user' | 'assistant' | 'tool';
20
21
  readonly content: string;
22
+ readonly anthropicContent?: readonly unknown[];
21
23
  readonly toolCalls?: readonly GatewayToolCall[];
22
24
  readonly toolCallId?: string;
23
25
  }
@@ -25,6 +27,7 @@ export interface GatewayChatMessage {
25
27
  export interface GatewayChatResponse {
26
28
  readonly text: string;
27
29
  readonly toolCalls: readonly GatewayToolCall[];
30
+ readonly anthropicContent?: readonly unknown[];
28
31
  }
29
32
 
30
33
  class GatewayHttpError extends Error {
@@ -37,14 +40,32 @@ class GatewayHttpError extends Error {
37
40
  }
38
41
  }
39
42
 
40
- export async function createGatewayChatCompletion(input: {
43
+ interface GatewayChatCompletionInput {
44
+ readonly protocol: GatewayProtocol;
41
45
  readonly baseUrl: string;
42
46
  readonly apiKey: string;
43
47
  readonly headers: Readonly<Record<string, string>>;
44
48
  readonly model: string;
49
+ readonly maxTokens?: number;
45
50
  readonly messages: readonly GatewayChatMessage[];
46
51
  readonly tools: readonly GatewayChatToolDefinition[];
47
- }): Promise<GatewayChatResponse> {
52
+ }
53
+
54
+ export function createGatewayChatCompletion(
55
+ input: GatewayChatCompletionInput
56
+ ): Promise<GatewayChatResponse> {
57
+ if (input.protocol === 'anthropic') {
58
+ if (input.maxTokens === undefined) {
59
+ throw new Error('Gateway Anthropic requests require maxTokens.');
60
+ }
61
+ return createAnthropicChatCompletion({ ...input, maxTokens: input.maxTokens });
62
+ }
63
+ return createOpenAIChatCompletion(input);
64
+ }
65
+
66
+ async function createOpenAIChatCompletion(
67
+ input: GatewayChatCompletionInput
68
+ ): Promise<GatewayChatResponse> {
48
69
  const response = await fetch(`${input.baseUrl}/chat/completions`, {
49
70
  method: 'POST',
50
71
  headers: {
@@ -54,7 +75,8 @@ export async function createGatewayChatCompletion(input: {
54
75
  },
55
76
  body: JSON.stringify({
56
77
  model: input.model,
57
- messages: input.messages.map((message) => serializeMessage(message)),
78
+ ...(input.maxTokens === undefined ? {} : { max_tokens: input.maxTokens }),
79
+ messages: input.messages.map((message) => serializeOpenAIMessage(message)),
58
80
  tools: input.tools,
59
81
  tool_choice: 'auto',
60
82
  temperature: 0,
@@ -85,7 +107,51 @@ export async function createGatewayChatCompletion(input: {
85
107
  };
86
108
  }
87
109
 
88
- function serializeMessage(message: GatewayChatMessage): Record<string, unknown> {
110
+ async function createAnthropicChatCompletion(
111
+ input: GatewayChatCompletionInput & { readonly maxTokens: number }
112
+ ): Promise<GatewayChatResponse> {
113
+ const system = input.messages
114
+ .filter((message) => message.role === 'system')
115
+ .map((message) => message.content)
116
+ .join('\n\n');
117
+ const response = await fetch(`${input.baseUrl}/v1/messages`, {
118
+ method: 'POST',
119
+ headers: {
120
+ 'Content-Type': 'application/json',
121
+ 'x-api-key': input.apiKey,
122
+ 'anthropic-version': '2023-06-01',
123
+ ...input.headers,
124
+ },
125
+ body: JSON.stringify({
126
+ model: input.model,
127
+ max_tokens: input.maxTokens,
128
+ ...(system ? { system } : {}),
129
+ messages: serializeAnthropicMessages(input.messages),
130
+ tools: input.tools.map((tool) => ({
131
+ name: tool.function.name,
132
+ description: tool.function.description,
133
+ input_schema: tool.function.parameters,
134
+ })),
135
+ }),
136
+ });
137
+
138
+ const bodyText = await response.text();
139
+ const parsed = tryParseJson(bodyText);
140
+ if (!response.ok) {
141
+ throw httpError(response.status, parsed ?? bodyText);
142
+ }
143
+ if (!isRecord(parsed)) {
144
+ throw new Error('Gateway returned a non-JSON response.');
145
+ }
146
+ const anthropicContent = getArray(parsed, 'content');
147
+ return {
148
+ text: getAnthropicMessageText(parsed),
149
+ toolCalls: getAnthropicToolCalls(parsed),
150
+ anthropicContent,
151
+ };
152
+ }
153
+
154
+ function serializeOpenAIMessage(message: GatewayChatMessage): Record<string, unknown> {
89
155
  if (message.role === 'assistant' && message.toolCalls && message.toolCalls.length > 0) {
90
156
  return {
91
157
  role: 'assistant',
@@ -113,6 +179,48 @@ function serializeMessage(message: GatewayChatMessage): Record<string, unknown>
113
179
  };
114
180
  }
115
181
 
182
+ function serializeAnthropicMessages(
183
+ messages: readonly GatewayChatMessage[]
184
+ ): readonly Record<string, unknown>[] {
185
+ const result: Record<string, unknown>[] = [];
186
+ let pendingToolResults: Record<string, unknown>[] | undefined;
187
+ for (const message of messages) {
188
+ if (message.role === 'system') continue;
189
+ if (message.role === 'tool') {
190
+ const toolResult = {
191
+ type: 'tool_result',
192
+ tool_use_id: message.toolCallId,
193
+ content: message.content,
194
+ };
195
+ if (pendingToolResults) {
196
+ pendingToolResults.push(toolResult);
197
+ } else {
198
+ pendingToolResults = [toolResult];
199
+ result.push({ role: 'user', content: pendingToolResults });
200
+ }
201
+ continue;
202
+ }
203
+ pendingToolResults = undefined;
204
+ if (message.role === 'assistant' && message.toolCalls && message.toolCalls.length > 0) {
205
+ result.push({
206
+ role: 'assistant',
207
+ content: message.anthropicContent ?? [
208
+ ...(message.content ? [{ type: 'text', text: message.content }] : []),
209
+ ...message.toolCalls.map((toolCall) => ({
210
+ type: 'tool_use',
211
+ id: toolCall.id,
212
+ name: toolCall.name,
213
+ input: tryParseJson(toolCall.argumentsText) ?? {},
214
+ })),
215
+ ],
216
+ });
217
+ continue;
218
+ }
219
+ result.push({ role: message.role, content: message.content });
220
+ }
221
+ return result;
222
+ }
223
+
116
224
  function getGatewayMessageText(message: Record<string, unknown>): string {
117
225
  const content = message.content;
118
226
  if (typeof content === 'string') return content;
@@ -143,6 +251,29 @@ function getGatewayToolCalls(message: Record<string, unknown>): readonly Gateway
143
251
  return result;
144
252
  }
145
253
 
254
+ function getAnthropicMessageText(message: Record<string, unknown>): string {
255
+ return getArray(message, 'content')
256
+ .filter((item) => isRecord(item) && getString(item, 'type') === 'text')
257
+ .map((item) => (isRecord(item) ? getString(item, 'text') ?? '' : ''))
258
+ .join('');
259
+ }
260
+
261
+ function getAnthropicToolCalls(message: Record<string, unknown>): readonly GatewayToolCall[] {
262
+ const result: GatewayToolCall[] = [];
263
+ for (const item of getArray(message, 'content')) {
264
+ if (!isRecord(item) || getString(item, 'type') !== 'tool_use') continue;
265
+ const id = getString(item, 'id');
266
+ const name = getString(item, 'name');
267
+ if (!id || !name) continue;
268
+ result.push({
269
+ id,
270
+ name,
271
+ argumentsText: JSON.stringify(isRecord(item.input) ? item.input : {}),
272
+ });
273
+ }
274
+ return result;
275
+ }
276
+
146
277
  function httpError(status: number, body: unknown): GatewayHttpError {
147
278
  return new GatewayHttpError(status, buildGatewayErrorMessage(status, body));
148
279
  }
@@ -164,10 +164,12 @@ async function runGatewayLoop(
164
164
 
165
165
  for (let turn = 0; turn < MAX_GATEWAY_TURNS; turn += 1) {
166
166
  const response = await createGatewayChatCompletion({
167
+ protocol: gateway.protocol,
167
168
  baseUrl: gateway.baseUrl,
168
169
  apiKey: gateway.apiKey,
169
170
  headers: gateway.headers,
170
171
  model: gateway.model,
172
+ ...(gateway.maxTokens === undefined ? {} : { maxTokens: gateway.maxTokens }),
171
173
  messages,
172
174
  tools: TOOL_DEFINITIONS,
173
175
  });
@@ -184,6 +186,9 @@ async function runGatewayLoop(
184
186
  messages.push({
185
187
  role: 'assistant',
186
188
  content: response.text,
189
+ ...(response.anthropicContent === undefined
190
+ ? {}
191
+ : { anthropicContent: response.anthropicContent }),
187
192
  toolCalls: response.toolCalls,
188
193
  });
189
194
 
@@ -5,6 +5,7 @@ import { contractError, invalidField } from './contract-errors';
5
5
  import { getString, isRecord, unknownToMessage } from './json';
6
6
  import type {
7
7
  GatewayBuildOptions,
8
+ GatewayProtocol,
8
9
  GatewayToolPolicy,
9
10
  ResolvedGatewayBuildOptions,
10
11
  } from './types';
@@ -48,15 +49,19 @@ export function normalizeGatewayBuildOptions(
48
49
  }
49
50
 
50
51
  const result: Record<string, unknown> = {};
52
+ const protocol = optionalGatewayProtocol(value.protocol, `${field}.protocol`);
51
53
  const baseUrl = optionalString(value.baseUrl, `${field}.baseUrl`);
52
54
  const apiKey = optionalString(value.apiKey, `${field}.apiKey`);
53
55
  const model = optionalNullableString(value.model, `${field}.model`);
56
+ const maxTokens = optionalNullablePositiveInteger(value.maxTokens, `${field}.maxTokens`);
54
57
  const headers = optionalStringRecord(value.headers, `${field}.headers`);
55
58
  const toolPolicy = optionalGatewayToolPolicy(value.toolPolicy, `${field}.toolPolicy`, cwd);
56
59
 
60
+ if (protocol !== undefined) result.protocol = protocol;
57
61
  if (baseUrl !== undefined) result.baseUrl = baseUrl;
58
62
  if (typeof apiKey === 'string') result.apiKey = apiKey;
59
63
  if (model !== undefined) result.model = model;
64
+ if (maxTokens !== undefined) result.maxTokens = maxTokens;
60
65
  if (headers !== undefined) result.headers = headers;
61
66
  if (toolPolicy !== undefined) result.toolPolicy = toolPolicy;
62
67
 
@@ -76,27 +81,51 @@ export function resolveGatewayConfiguration(
76
81
  message: `${field} is required for the gateway provider.`,
77
82
  });
78
83
  }
84
+ const protocol = value.protocol ?? 'openai';
79
85
  const baseUrl = requiredNonEmptyString(value.baseUrl, `${field}.baseUrl`);
80
86
  const apiKey = requiredNonEmptyString(value.apiKey, `${field}.apiKey`);
81
87
  const model = requiredNonEmptyString(value.model, `${field}.model`);
88
+ const maxTokens = value.maxTokens;
82
89
  const headers = value.headers ?? {};
83
90
  const toolPolicy = requiredGatewayToolPolicy(value.toolPolicy, `${field}.toolPolicy`, cwd);
84
91
 
92
+ if (protocol === 'anthropic' && maxTokens === undefined) {
93
+ invalidField(
94
+ `${field}.maxTokens`,
95
+ `${field}.maxTokens is required when ${field}.protocol is "anthropic".`
96
+ );
97
+ }
85
98
  assertValidGatewayBaseUrl(baseUrl, `${field}.baseUrl`);
86
99
  return {
100
+ protocol,
87
101
  baseUrl: normalizeBaseUrl(baseUrl),
88
102
  apiKey,
89
103
  headers,
90
104
  model,
105
+ ...(maxTokens === undefined ? {} : { maxTokens }),
91
106
  toolPolicy,
92
107
  };
93
108
  }
94
109
 
95
110
  export function validateGatewaySettings(settings: Record<string, unknown>): string | null {
96
111
  try {
112
+ const protocol = optionalGatewayProtocol(
113
+ settings.protocol,
114
+ 'providerSettings.gateway.protocol'
115
+ );
97
116
  optionalString(settings.baseUrl, 'providerSettings.gateway.baseUrl');
98
117
  optionalString(settings.apiKey, 'providerSettings.gateway.apiKey');
99
118
  optionalNullableString(settings.model, 'providerSettings.gateway.model');
119
+ const maxTokens = optionalNullablePositiveInteger(
120
+ settings.maxTokens,
121
+ 'providerSettings.gateway.maxTokens'
122
+ );
123
+ if (protocol === 'anthropic' && maxTokens === undefined) {
124
+ invalidField(
125
+ 'providerSettings.gateway.maxTokens',
126
+ 'providerSettings.gateway.maxTokens is required when providerSettings.gateway.protocol is "anthropic".'
127
+ );
128
+ }
100
129
  optionalStringRecord(settings.headers, 'providerSettings.gateway.headers');
101
130
  optionalGatewayToolPolicy(
102
131
  settings.toolPolicy,
@@ -595,6 +624,17 @@ function optionalFiniteInteger(value: unknown, field: string): number | undefine
595
624
  return requiredFiniteInteger(value, field);
596
625
  }
597
626
 
627
+ function optionalNullablePositiveInteger(value: unknown, field: string): number | undefined {
628
+ if (value === undefined || value === null) return undefined;
629
+ return requiredFiniteInteger(value, field);
630
+ }
631
+
632
+ function optionalGatewayProtocol(value: unknown, field: string): GatewayProtocol | undefined {
633
+ if (value === undefined || value === null) return undefined;
634
+ if (value === 'openai' || value === 'anthropic') return value;
635
+ invalidField(field, `${field} must be "openai" or "anthropic".`);
636
+ }
637
+
598
638
  function requiredFiniteInteger(value: unknown, field: string): number {
599
639
  if (typeof value === 'number' && Number.isInteger(value) && value > 0) return value;
600
640
  invalidField(field, `${field} must be a positive integer.`);
@@ -68,6 +68,7 @@ export type {
68
68
  ErrorClassificationKind,
69
69
  GatewayBuildOptions,
70
70
  GatewayCliFeatures,
71
+ GatewayProtocol,
71
72
  GatewayToolPolicy,
72
73
  GeminiCliFeatures,
73
74
  KnownProviderName,
@@ -227,10 +227,18 @@ export const providerRegistry = [
227
227
  invoke: SPAWN_INVOKE,
228
228
  installInstructions: 'Bundled with Zeroshot; no external provider CLI install is required.',
229
229
  authInstructions:
230
- 'Configure providerSettings.gateway.baseUrl, apiKey, model, and toolPolicy in Zeroshot settings.',
230
+ 'Configure providerSettings.gateway.protocol, baseUrl, apiKey, model, maxTokens when required, and toolPolicy in Zeroshot settings.',
231
231
  credentialPaths: [],
232
232
  credentialEnvKeys: gatewayAdapter.credentialEnvKeys,
233
- settingsFields: ['baseUrl', 'apiKey', 'headers', 'model', 'toolPolicy'],
233
+ settingsFields: [
234
+ 'protocol',
235
+ 'baseUrl',
236
+ 'apiKey',
237
+ 'headers',
238
+ 'model',
239
+ 'maxTokens',
240
+ 'toolPolicy',
241
+ ],
234
242
  settingsDefaults: gatewaySettingsDefaults,
235
243
  settingsValidator: validateGatewaySettings,
236
244
  capabilities: {
@@ -228,6 +228,9 @@ function resolveRuntimeGatewayOptions(
228
228
  ? settingsGateway.headers
229
229
  : { ...(settingsGateway.headers ?? {}), ...requestGateway.headers };
230
230
  const mergedGateway: GatewayBuildOptions = {
231
+ ...(requestGateway.protocol ?? settingsGateway.protocol
232
+ ? { protocol: requestGateway.protocol ?? settingsGateway.protocol }
233
+ : {}),
231
234
  ...((requestGateway.baseUrl ?? settingsGateway.baseUrl)
232
235
  ? { baseUrl: requestGateway.baseUrl ?? settingsGateway.baseUrl }
233
236
  : {}),
@@ -236,6 +239,9 @@ function resolveRuntimeGatewayOptions(
236
239
  : {}),
237
240
  ...(mergedHeaders === undefined ? {} : { headers: mergedHeaders }),
238
241
  model: requestGateway.model ?? modelSpec.model ?? settingsGateway.model ?? null,
242
+ ...(requestGateway.maxTokens ?? settingsGateway.maxTokens
243
+ ? { maxTokens: requestGateway.maxTokens ?? settingsGateway.maxTokens }
244
+ : {}),
239
245
  ...((requestGateway.toolPolicy ?? settingsGateway.toolPolicy)
240
246
  ? { toolPolicy: requestGateway.toolPolicy ?? settingsGateway.toolPolicy }
241
247
  : {}),
@@ -17,6 +17,7 @@ export type KnownProviderName = ProviderId | ProviderAlias;
17
17
  export type ModelLevel = 'level1' | 'level2' | 'level3';
18
18
  export type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
19
19
  export type OutputFormat = 'text' | 'json' | 'stream-json';
20
+ export type GatewayProtocol = 'openai' | 'anthropic';
20
21
  export type {
21
22
  ProviderCapabilities,
22
23
  ProviderCapabilityState,
@@ -66,18 +67,22 @@ export interface GatewayToolPolicy {
66
67
  }
67
68
 
68
69
  export interface GatewayBuildOptions {
70
+ readonly protocol?: GatewayProtocol;
69
71
  readonly baseUrl?: string;
70
72
  readonly apiKey?: string;
71
73
  readonly headers?: Readonly<Record<string, string>>;
72
74
  readonly model?: string | null;
75
+ readonly maxTokens?: number;
73
76
  readonly toolPolicy?: GatewayToolPolicy;
74
77
  }
75
78
 
76
79
  export interface ResolvedGatewayBuildOptions {
80
+ readonly protocol: GatewayProtocol;
77
81
  readonly baseUrl: string;
78
82
  readonly apiKey: string;
79
83
  readonly headers: Readonly<Record<string, string>>;
80
84
  readonly model: string;
85
+ readonly maxTokens?: number;
81
86
  readonly toolPolicy: GatewayToolPolicy;
82
87
  }
83
88
 
package/src/preflight.js CHANGED
@@ -426,11 +426,12 @@ function validateGatewayProvider() {
426
426
  errors: [
427
427
  formatError(
428
428
  'Gateway provider not configured',
429
- 'providerSettings.gateway must define baseUrl, apiKey, model, and toolPolicy before gateway can run.',
429
+ 'providerSettings.gateway must define protocol, baseUrl, apiKey, model, and toolPolicy before gateway can run.',
430
430
  [
431
431
  'Run: zeroshot settings',
432
- 'Set providerSettings.gateway.baseUrl to your OpenAI-compatible endpoint base URL',
432
+ 'Set providerSettings.gateway.protocol and baseUrl for your compatible endpoint',
433
433
  'Set providerSettings.gateway.apiKey and providerSettings.gateway.model',
434
+ 'Set providerSettings.gateway.maxTokens when protocol is anthropic',
434
435
  'Set providerSettings.gateway.toolPolicy.roots and toolPolicy.commands explicitly',
435
436
  ]
436
437
  ),