@the-open-engine/zeroshot 6.4.0 → 6.6.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/cli/commands/providers.js +11 -5
- package/cli/index.js +79 -16
- package/cli/lib/first-run.js +14 -3
- package/docker/zeroshot-cluster/Dockerfile +11 -1
- package/lib/agent-cli-provider/acp-stdio-runner.d.ts +4 -0
- package/lib/agent-cli-provider/acp-stdio-runner.d.ts.map +1 -0
- package/lib/agent-cli-provider/acp-stdio-runner.js +259 -0
- package/lib/agent-cli-provider/acp-stdio-runner.js.map +1 -0
- package/lib/agent-cli-provider/adapters/acp.d.ts +23 -0
- package/lib/agent-cli-provider/adapters/acp.d.ts.map +1 -0
- package/lib/agent-cli-provider/adapters/acp.js +373 -0
- package/lib/agent-cli-provider/adapters/acp.js.map +1 -0
- package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +2 -29
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/adapters/copilot-parser.d.ts +3 -0
- package/lib/agent-cli-provider/adapters/copilot-parser.d.ts.map +1 -0
- package/lib/agent-cli-provider/adapters/copilot-parser.js +154 -0
- package/lib/agent-cli-provider/adapters/copilot-parser.js.map +1 -0
- package/lib/agent-cli-provider/adapters/copilot.d.ts +3 -0
- package/lib/agent-cli-provider/adapters/copilot.d.ts.map +1 -0
- package/lib/agent-cli-provider/adapters/copilot.js +176 -0
- package/lib/agent-cli-provider/adapters/copilot.js.map +1 -0
- package/lib/agent-cli-provider/adapters/gateway.d.ts +6 -0
- package/lib/agent-cli-provider/adapters/gateway.d.ts.map +1 -0
- package/lib/agent-cli-provider/adapters/gateway.js +161 -0
- package/lib/agent-cli-provider/adapters/gateway.js.map +1 -0
- package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/gemini.js +9 -2
- package/lib/agent-cli-provider/adapters/gemini.js.map +1 -1
- package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/index.js +6 -34
- package/lib/agent-cli-provider/adapters/index.js.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode.js +5 -1
- package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
- package/lib/agent-cli-provider/adapters/pi.d.ts +3 -0
- package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -0
- package/lib/agent-cli-provider/adapters/pi.js +331 -0
- package/lib/agent-cli-provider/adapters/pi.js.map +1 -0
- package/lib/agent-cli-provider/claude-command.d.ts +6 -0
- package/lib/agent-cli-provider/claude-command.d.ts.map +1 -0
- package/lib/agent-cli-provider/claude-command.js +43 -0
- package/lib/agent-cli-provider/claude-command.js.map +1 -0
- package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-actions.js +13 -17
- package/lib/agent-cli-provider/contract-actions.js.map +1 -1
- package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-invoke.js +10 -3
- package/lib/agent-cli-provider/contract-invoke.js.map +1 -1
- package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-options.js +57 -2
- package/lib/agent-cli-provider/contract-options.js.map +1 -1
- package/lib/agent-cli-provider/contract-support.d.ts +1 -0
- package/lib/agent-cli-provider/contract-support.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-support.js +25 -3
- package/lib/agent-cli-provider/contract-support.js.map +1 -1
- package/lib/agent-cli-provider/errors.d.ts +1 -0
- package/lib/agent-cli-provider/errors.d.ts.map +1 -1
- package/lib/agent-cli-provider/errors.js +14 -5
- package/lib/agent-cli-provider/errors.js.map +1 -1
- package/lib/agent-cli-provider/gateway-client.d.ts +32 -0
- package/lib/agent-cli-provider/gateway-client.d.ts.map +1 -0
- package/lib/agent-cli-provider/gateway-client.js +135 -0
- package/lib/agent-cli-provider/gateway-client.js.map +1 -0
- package/lib/agent-cli-provider/gateway-runner.d.ts +9 -0
- package/lib/agent-cli-provider/gateway-runner.d.ts.map +1 -0
- package/lib/agent-cli-provider/gateway-runner.js +294 -0
- package/lib/agent-cli-provider/gateway-runner.js.map +1 -0
- package/lib/agent-cli-provider/gateway-tools.d.ts +14 -0
- package/lib/agent-cli-provider/gateway-tools.d.ts.map +1 -0
- package/lib/agent-cli-provider/gateway-tools.js +495 -0
- package/lib/agent-cli-provider/gateway-tools.js.map +1 -0
- package/lib/agent-cli-provider/index.d.ts +3 -2
- package/lib/agent-cli-provider/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/index.js +14 -1
- package/lib/agent-cli-provider/index.js.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.d.ts +2 -2
- package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
- package/lib/agent-cli-provider/invoke-evidence.js +17 -3
- package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +437 -0
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -0
- package/lib/agent-cli-provider/provider-registry.js +411 -0
- package/lib/agent-cli-provider/provider-registry.js.map +1 -0
- package/lib/agent-cli-provider/single-agent-runtime.d.ts +7 -0
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +139 -19
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +106 -4
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/docker-config.js +24 -13
- package/lib/provider-defaults.js +15 -4
- package/lib/provider-detection.js +2 -0
- package/lib/provider-names.js +32 -12
- package/lib/repo-settings.js +15 -1
- package/lib/settings/claude-auth.js +3 -6
- package/lib/settings.js +17 -2
- package/lib/setup-apply.js +300 -0
- package/lib/setup-journal.js +109 -0
- package/lib/setup-plan.js +406 -0
- package/lib/setup-undo.js +88 -0
- package/lib/start-cluster.js +12 -1
- package/lib/stream-json-parser.js +7 -5
- package/package.json +31 -1
- package/scripts/assert-release-published.js +53 -0
- package/scripts/live-provider-smoke.js +221 -0
- package/scripts/release-preflight.js +236 -0
- package/src/agent/agent-lifecycle.js +5 -4
- package/src/agent/agent-quality-gate-schema.js +2 -2
- package/src/agent/agent-task-executor.js +41 -2
- package/src/agent-cli-provider/acp-stdio-runner.ts +314 -0
- package/src/agent-cli-provider/adapters/acp.ts +493 -0
- package/src/agent-cli-provider/adapters/claude.ts +2 -32
- package/src/agent-cli-provider/adapters/copilot-parser.ts +166 -0
- package/src/agent-cli-provider/adapters/copilot.ts +231 -0
- package/src/agent-cli-provider/adapters/gateway.ts +186 -0
- package/src/agent-cli-provider/adapters/gemini.ts +9 -2
- package/src/agent-cli-provider/adapters/index.ts +5 -36
- package/src/agent-cli-provider/adapters/opencode.ts +4 -1
- package/src/agent-cli-provider/adapters/pi.ts +410 -0
- package/src/agent-cli-provider/claude-command.ts +47 -0
- package/src/agent-cli-provider/contract-actions.ts +14 -17
- package/src/agent-cli-provider/contract-invoke.ts +12 -3
- package/src/agent-cli-provider/contract-options.ts +72 -3
- package/src/agent-cli-provider/contract-support.ts +30 -3
- package/src/agent-cli-provider/errors.ts +14 -4
- package/src/agent-cli-provider/gateway-client.ts +170 -0
- package/src/agent-cli-provider/gateway-runner.ts +353 -0
- package/src/agent-cli-provider/gateway-tools.ts +616 -0
- package/src/agent-cli-provider/index.ts +28 -0
- package/src/agent-cli-provider/invoke-evidence.ts +24 -3
- package/src/agent-cli-provider/provider-registry.ts +515 -0
- package/src/agent-cli-provider/single-agent-runtime.ts +192 -18
- package/src/agent-cli-provider/types.ts +145 -4
- package/src/config-validator.js +33 -15
- package/src/isolation-manager.js +124 -19
- package/src/orchestrator.js +23 -10
- package/src/preflight.js +83 -30
- package/src/providers/capabilities.js +15 -42
- package/src/providers/index.js +52 -72
- package/src/worktree-claude-config.js +17 -0
- package/task-lib/commands/run.js +1 -0
- package/task-lib/provider-helper-runtime.js +11 -0
- package/task-lib/runner.js +7 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isRecord } from './json';
|
|
2
2
|
import { invalidField, contractError } from './contract-errors';
|
|
3
3
|
import { stringRecord } from './contract-env';
|
|
4
|
+
import { normalizeGatewayBuildOptions } from './gateway-tools';
|
|
4
5
|
import type {
|
|
5
6
|
BuildProviderCommandOptions,
|
|
6
7
|
CliFeatureOverrides,
|
|
@@ -23,14 +24,46 @@ const CLI_FEATURE_FIELDS = [
|
|
|
23
24
|
'supportsModel',
|
|
24
25
|
'supportsJson',
|
|
25
26
|
'supportsOutputSchema',
|
|
27
|
+
'supportsDir',
|
|
26
28
|
'supportsCwd',
|
|
27
29
|
'supportsConfigOverride',
|
|
28
30
|
'supportsSkipGitRepoCheck',
|
|
29
31
|
'supportsVariant',
|
|
32
|
+
'supportsJsonMode',
|
|
33
|
+
'supportsNoSession',
|
|
34
|
+
'supportsNoExtensions',
|
|
35
|
+
'supportsNoSkills',
|
|
36
|
+
'supportsNoPromptTemplates',
|
|
37
|
+
'supportsNoContextFiles',
|
|
38
|
+
'supportsNoApprove',
|
|
39
|
+
'supportsJsonOutput',
|
|
40
|
+
'supportsAllowAll',
|
|
41
|
+
'supportsNoAskUser',
|
|
42
|
+
'supportsAddDir',
|
|
43
|
+
'supportsMcpConfig',
|
|
44
|
+
'supportsBundledRunner',
|
|
45
|
+
'supportsAcpStdio',
|
|
46
|
+
'supportsPromptImages',
|
|
47
|
+
'supportsLoadSession',
|
|
48
|
+
'supportsSessionCancel',
|
|
49
|
+
'supportsSessionSetModel',
|
|
50
|
+
'supportsSessionSetMode',
|
|
51
|
+
'supportsRemoteTransport',
|
|
52
|
+
'supportsCustomTransport',
|
|
53
|
+
'supportsPermissionRequests',
|
|
54
|
+
'supportsFsTools',
|
|
55
|
+
'supportsTerminalTools',
|
|
30
56
|
'unknown',
|
|
31
57
|
] as const;
|
|
32
58
|
|
|
33
59
|
type CliFeatureField = (typeof CLI_FEATURE_FIELDS)[number];
|
|
60
|
+
const FALSE_ONLY_CLI_FEATURE_FIELDS = new Set<CliFeatureField>([
|
|
61
|
+
'supportsRemoteTransport',
|
|
62
|
+
'supportsCustomTransport',
|
|
63
|
+
'supportsPermissionRequests',
|
|
64
|
+
'supportsFsTools',
|
|
65
|
+
'supportsTerminalTools',
|
|
66
|
+
]);
|
|
34
67
|
|
|
35
68
|
export function requestOptions(value: unknown): BuildProviderCommandOptions {
|
|
36
69
|
if (value === undefined) return {};
|
|
@@ -77,6 +110,25 @@ function optionalBooleanValue(value: unknown, field: string): boolean | undefine
|
|
|
77
110
|
invalidField(field, `${field} must be a boolean.`);
|
|
78
111
|
}
|
|
79
112
|
|
|
113
|
+
function optionalMcpConfig(value: unknown): readonly string[] | undefined {
|
|
114
|
+
if (value === undefined) return undefined;
|
|
115
|
+
if (!Array.isArray(value)) {
|
|
116
|
+
invalidField('options.mcpConfig', 'options.mcpConfig must be an array of strings.');
|
|
117
|
+
}
|
|
118
|
+
return value.map((item, index) => {
|
|
119
|
+
if (typeof item !== 'string') {
|
|
120
|
+
invalidField(`options.mcpConfig[${index}]`, `options.mcpConfig[${index}] must be a string.`);
|
|
121
|
+
}
|
|
122
|
+
if (item.trim().length === 0) {
|
|
123
|
+
invalidField(
|
|
124
|
+
`options.mcpConfig[${index}]`,
|
|
125
|
+
`options.mcpConfig[${index}] must be a non-empty string.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return item;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
80
132
|
function optionalEnumValue<T extends string>(
|
|
81
133
|
value: unknown,
|
|
82
134
|
field: string,
|
|
@@ -119,12 +171,19 @@ function optionalCliFeatures(value: unknown): CliFeatureOverrides | undefined {
|
|
|
119
171
|
invalidField('options.cliFeatures', 'options.cliFeatures must be an object.');
|
|
120
172
|
}
|
|
121
173
|
|
|
122
|
-
const result:
|
|
174
|
+
const result: Record<string, boolean> = {};
|
|
123
175
|
for (const field of CLI_FEATURE_FIELDS) {
|
|
124
176
|
const item = optionalBooleanValue(value[field], `options.cliFeatures.${field}`);
|
|
125
|
-
if (item
|
|
177
|
+
if (item === undefined) continue;
|
|
178
|
+
if (item && FALSE_ONLY_CLI_FEATURE_FIELDS.has(field)) {
|
|
179
|
+
invalidField(
|
|
180
|
+
`options.cliFeatures.${field}`,
|
|
181
|
+
`options.cliFeatures.${field} must be false when provided.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
result[field] = item;
|
|
126
185
|
}
|
|
127
|
-
return result;
|
|
186
|
+
return result as CliFeatureOverrides;
|
|
128
187
|
}
|
|
129
188
|
|
|
130
189
|
function normalizeBuildOptions(value: Record<string, unknown>): BuildProviderCommandOptions {
|
|
@@ -149,11 +208,21 @@ function normalizeBuildOptions(value: Record<string, unknown>): BuildProviderCom
|
|
|
149
208
|
optionalBooleanValue(value.continueSession, 'options.continueSession')
|
|
150
209
|
);
|
|
151
210
|
addDefined(result, 'cliFeatures', optionalCliFeatures(value.cliFeatures));
|
|
211
|
+
addDefined(result, 'mcpConfig', optionalMcpConfig(value.mcpConfig));
|
|
152
212
|
addDefined(
|
|
153
213
|
result,
|
|
154
214
|
'strictSchema',
|
|
155
215
|
optionalBooleanValue(value.strictSchema, 'options.strictSchema')
|
|
156
216
|
);
|
|
217
|
+
addDefined(
|
|
218
|
+
result,
|
|
219
|
+
'gateway',
|
|
220
|
+
normalizeGatewayBuildOptions(
|
|
221
|
+
value.gateway,
|
|
222
|
+
'options.gateway',
|
|
223
|
+
optionalStringValue(value.cwd, 'options.cwd') ?? process.cwd()
|
|
224
|
+
)
|
|
225
|
+
);
|
|
157
226
|
if (Object.prototype.hasOwnProperty.call(value, 'authEnv')) {
|
|
158
227
|
result.authEnv = stringRecord(value.authEnv, 'options.authEnv');
|
|
159
228
|
}
|
|
@@ -40,10 +40,10 @@ export function adapterForProvider(provider: string | null): ProviderAdapter {
|
|
|
40
40
|
}
|
|
41
41
|
try {
|
|
42
42
|
return getProviderAdapter(provider);
|
|
43
|
-
} catch {
|
|
43
|
+
} catch (error) {
|
|
44
44
|
throw contractError({
|
|
45
45
|
code: 'unknown-provider',
|
|
46
|
-
message: `Unknown provider: ${provider}.`,
|
|
46
|
+
message: error instanceof Error ? error.message : `Unknown provider: ${provider}.`,
|
|
47
47
|
exitCode: 4,
|
|
48
48
|
field: 'provider',
|
|
49
49
|
});
|
|
@@ -54,7 +54,17 @@ function mergeCommandSpec(
|
|
|
54
54
|
commandSpec: CommandSpec,
|
|
55
55
|
env: Readonly<Record<string, string>>
|
|
56
56
|
): CommandSpec {
|
|
57
|
-
const
|
|
57
|
+
const overriddenKey = findReservedCommandEnvKey(commandSpec.env, env);
|
|
58
|
+
if (overriddenKey !== null) {
|
|
59
|
+
throw contractError({
|
|
60
|
+
code: 'forbidden-field',
|
|
61
|
+
message: `env.${overriddenKey.requestKey} is not accepted by provider executable requests; provider adapters own ${overriddenKey.commandKey} and other runner control/auth environment variables.`,
|
|
62
|
+
exitCode: 2,
|
|
63
|
+
field: `env.${overriddenKey.requestKey}`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const mergedEnv = { ...env, ...commandSpec.env };
|
|
58
68
|
return {
|
|
59
69
|
...commandSpec,
|
|
60
70
|
env: mergedEnv,
|
|
@@ -62,6 +72,21 @@ function mergeCommandSpec(
|
|
|
62
72
|
};
|
|
63
73
|
}
|
|
64
74
|
|
|
75
|
+
function findReservedCommandEnvKey(
|
|
76
|
+
commandEnv: Readonly<Record<string, string>>,
|
|
77
|
+
requestedEnv: Readonly<Record<string, string>>
|
|
78
|
+
): { readonly requestKey: string; readonly commandKey: string } | null {
|
|
79
|
+
const commandEnvKeys = new Map<string, string>();
|
|
80
|
+
for (const key of Object.keys(commandEnv)) {
|
|
81
|
+
commandEnvKeys.set(key.toLowerCase(), key);
|
|
82
|
+
}
|
|
83
|
+
for (const key of Object.keys(requestedEnv)) {
|
|
84
|
+
const commandKey = commandEnvKeys.get(key.toLowerCase());
|
|
85
|
+
if (commandKey !== undefined) return { requestKey: key, commandKey };
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
65
90
|
function buildOptions(request: RequestData): BuildProviderCommandOptions {
|
|
66
91
|
const options = requestOptions(request.raw.options);
|
|
67
92
|
const cwd = optionalString(request.raw, 'cwd');
|
|
@@ -73,6 +98,7 @@ export function buildCommandSpec(request: RequestData): {
|
|
|
73
98
|
readonly adapter: ProviderAdapter;
|
|
74
99
|
readonly commandSpec: CommandSpec;
|
|
75
100
|
readonly options: BuildProviderCommandOptions;
|
|
101
|
+
readonly context: string;
|
|
76
102
|
} {
|
|
77
103
|
const adapter = adapterForProvider(request.provider);
|
|
78
104
|
const context = requiredString(request.raw, 'context');
|
|
@@ -84,6 +110,7 @@ export function buildCommandSpec(request: RequestData): {
|
|
|
84
110
|
});
|
|
85
111
|
return {
|
|
86
112
|
adapter: prepared.adapter,
|
|
113
|
+
context,
|
|
87
114
|
options: prepared.options,
|
|
88
115
|
commandSpec: mergeCommandSpec(prepared.commandSpec, request.env),
|
|
89
116
|
};
|
|
@@ -34,14 +34,24 @@ const BASE_PERMANENT_PATTERNS: readonly RegExp[] = [
|
|
|
34
34
|
/insufficient quota/i,
|
|
35
35
|
];
|
|
36
36
|
|
|
37
|
-
function
|
|
37
|
+
function statusFromMessage(message: string): number | null {
|
|
38
|
+
const match = /\b(?:status(?: code)?|http)\s+(?<status>\d{3})\b/i.exec(message);
|
|
39
|
+
if (!match?.groups?.status) return null;
|
|
40
|
+
const status = Number.parseInt(match.groups.status, 10);
|
|
41
|
+
return Number.isInteger(status) ? status : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function extractErrorStatus(error: unknown): number | null {
|
|
38
45
|
if (!isRecord(error)) return null;
|
|
39
46
|
const directStatus = getNumber(error, 'status') ?? getNumber(error, 'statusCode');
|
|
40
47
|
if (directStatus !== null) return directStatus;
|
|
41
48
|
|
|
42
49
|
const response = getRecord(error, 'response');
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
const responseStatus =
|
|
51
|
+
response === null ? null : getNumber(response, 'status') ?? getNumber(response, 'statusCode');
|
|
52
|
+
if (responseStatus !== null) return responseStatus;
|
|
53
|
+
|
|
54
|
+
return statusFromMessage(unknownToMessage(error));
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
function getCode(error: unknown): string | null {
|
|
@@ -69,7 +79,7 @@ export function classifyErrorWithPatterns(
|
|
|
69
79
|
retryablePatterns: readonly RegExp[],
|
|
70
80
|
permanentPatterns: readonly RegExp[]
|
|
71
81
|
): ErrorClassification {
|
|
72
|
-
const statusClassification = classifyStatus(
|
|
82
|
+
const statusClassification = classifyStatus(extractErrorStatus(error));
|
|
73
83
|
if (statusClassification !== null) return statusClassification;
|
|
74
84
|
|
|
75
85
|
const codeClassification = classifyCode(getCode(error));
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { getArray, getRecord, getString, isRecord, unknownToMessage } from './json';
|
|
2
|
+
|
|
3
|
+
export interface GatewayChatToolDefinition {
|
|
4
|
+
readonly type: 'function';
|
|
5
|
+
readonly function: {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly description: string;
|
|
8
|
+
readonly parameters: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GatewayToolCall {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly argumentsText: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GatewayChatMessage {
|
|
19
|
+
readonly role: 'system' | 'user' | 'assistant' | 'tool';
|
|
20
|
+
readonly content: string;
|
|
21
|
+
readonly toolCalls?: readonly GatewayToolCall[];
|
|
22
|
+
readonly toolCallId?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface GatewayChatResponse {
|
|
26
|
+
readonly text: string;
|
|
27
|
+
readonly toolCalls: readonly GatewayToolCall[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class GatewayHttpError extends Error {
|
|
31
|
+
readonly status: number;
|
|
32
|
+
|
|
33
|
+
constructor(status: number, message: string) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = 'GatewayHttpError';
|
|
36
|
+
this.status = status;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function createGatewayChatCompletion(input: {
|
|
41
|
+
readonly baseUrl: string;
|
|
42
|
+
readonly apiKey: string;
|
|
43
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
44
|
+
readonly model: string;
|
|
45
|
+
readonly messages: readonly GatewayChatMessage[];
|
|
46
|
+
readonly tools: readonly GatewayChatToolDefinition[];
|
|
47
|
+
}): Promise<GatewayChatResponse> {
|
|
48
|
+
const response = await fetch(`${input.baseUrl}/chat/completions`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
Authorization: `Bearer ${input.apiKey}`,
|
|
53
|
+
...input.headers,
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
model: input.model,
|
|
57
|
+
messages: input.messages.map((message) => serializeMessage(message)),
|
|
58
|
+
tools: input.tools,
|
|
59
|
+
tool_choice: 'auto',
|
|
60
|
+
temperature: 0,
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const bodyText = await response.text();
|
|
65
|
+
const parsed = tryParseJson(bodyText);
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw httpError(response.status, parsed ?? bodyText);
|
|
68
|
+
}
|
|
69
|
+
if (!isRecord(parsed)) {
|
|
70
|
+
throw new Error('Gateway returned a non-JSON response.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const choice = getArray(parsed, 'choices')[0];
|
|
74
|
+
if (!isRecord(choice)) {
|
|
75
|
+
throw new Error('Gateway response did not include choices[0].');
|
|
76
|
+
}
|
|
77
|
+
const message = getRecord(choice, 'message');
|
|
78
|
+
if (message === null) {
|
|
79
|
+
throw new Error('Gateway response did not include choices[0].message.');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
text: getGatewayMessageText(message),
|
|
84
|
+
toolCalls: getGatewayToolCalls(message),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function serializeMessage(message: GatewayChatMessage): Record<string, unknown> {
|
|
89
|
+
if (message.role === 'assistant' && message.toolCalls && message.toolCalls.length > 0) {
|
|
90
|
+
return {
|
|
91
|
+
role: 'assistant',
|
|
92
|
+
content: message.content,
|
|
93
|
+
tool_calls: message.toolCalls.map((toolCall) => ({
|
|
94
|
+
id: toolCall.id,
|
|
95
|
+
type: 'function',
|
|
96
|
+
function: {
|
|
97
|
+
name: toolCall.name,
|
|
98
|
+
arguments: toolCall.argumentsText,
|
|
99
|
+
},
|
|
100
|
+
})),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (message.role === 'tool') {
|
|
104
|
+
return {
|
|
105
|
+
role: 'tool',
|
|
106
|
+
tool_call_id: message.toolCallId,
|
|
107
|
+
content: message.content,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
role: message.role,
|
|
112
|
+
content: message.content,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getGatewayMessageText(message: Record<string, unknown>): string {
|
|
117
|
+
const content = message.content;
|
|
118
|
+
if (typeof content === 'string') return content;
|
|
119
|
+
if (!Array.isArray(content)) return '';
|
|
120
|
+
|
|
121
|
+
let text = '';
|
|
122
|
+
for (const item of content) {
|
|
123
|
+
if (!isRecord(item)) continue;
|
|
124
|
+
const type = getString(item, 'type');
|
|
125
|
+
if (type === 'text') {
|
|
126
|
+
text += getString(item, 'text') ?? '';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return text;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getGatewayToolCalls(message: Record<string, unknown>): readonly GatewayToolCall[] {
|
|
133
|
+
const result: GatewayToolCall[] = [];
|
|
134
|
+
for (const item of getArray(message, 'tool_calls')) {
|
|
135
|
+
if (!isRecord(item)) continue;
|
|
136
|
+
const id = getString(item, 'id');
|
|
137
|
+
const fn = getRecord(item, 'function');
|
|
138
|
+
const name = fn === null ? null : getString(fn, 'name');
|
|
139
|
+
const argumentsText = fn === null ? null : getString(fn, 'arguments');
|
|
140
|
+
if (!id || !name || argumentsText === null) continue;
|
|
141
|
+
result.push({ id, name, argumentsText });
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function httpError(status: number, body: unknown): GatewayHttpError {
|
|
147
|
+
return new GatewayHttpError(status, buildGatewayErrorMessage(status, body));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildGatewayErrorMessage(status: number, body: unknown): string {
|
|
151
|
+
if (isRecord(body)) {
|
|
152
|
+
const nested = getRecord(body, 'error');
|
|
153
|
+
if (nested) {
|
|
154
|
+
const message = getString(nested, 'message');
|
|
155
|
+
if (message) return `Gateway request failed with status ${status}: ${message}`;
|
|
156
|
+
return `Gateway request failed with status ${status}: ${unknownToMessage(nested)}`;
|
|
157
|
+
}
|
|
158
|
+
const message = getString(body, 'message');
|
|
159
|
+
if (message) return `Gateway request failed with status ${status}: ${message}`;
|
|
160
|
+
}
|
|
161
|
+
return `Gateway request failed with status ${status}: ${unknownToMessage(body)}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function tryParseJson(value: string): unknown | null {
|
|
165
|
+
try {
|
|
166
|
+
return JSON.parse(value) as unknown;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|