@agentuity/core 2.0.14 → 2.0.15
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/services/aigateway/api-reference.d.ts +4 -0
- package/dist/services/aigateway/api-reference.d.ts.map +1 -0
- package/dist/services/aigateway/api-reference.js +146 -0
- package/dist/services/aigateway/api-reference.js.map +1 -0
- package/dist/services/aigateway/index.d.ts +2 -0
- package/dist/services/aigateway/index.d.ts.map +1 -0
- package/dist/services/aigateway/index.js +2 -0
- package/dist/services/aigateway/index.js.map +1 -0
- package/dist/services/aigateway/service.d.ts +221 -0
- package/dist/services/aigateway/service.d.ts.map +1 -0
- package/dist/services/aigateway/service.js +280 -0
- package/dist/services/aigateway/service.js.map +1 -0
- package/dist/services/api-reference.d.ts +1 -0
- package/dist/services/api-reference.d.ts.map +1 -1
- package/dist/services/api-reference.js.map +1 -1
- package/dist/services/coder/client.d.ts +5 -1
- package/dist/services/coder/client.d.ts.map +1 -1
- package/dist/services/coder/client.js +8 -1
- package/dist/services/coder/client.js.map +1 -1
- package/dist/services/coder/protocol.d.ts +1 -1
- package/dist/services/coder/sessions.d.ts +2 -0
- package/dist/services/coder/sessions.d.ts.map +1 -1
- package/dist/services/coder/skills.d.ts +4 -1
- package/dist/services/coder/skills.d.ts.map +1 -1
- package/dist/services/coder/skills.js +14 -1
- package/dist/services/coder/skills.js.map +1 -1
- package/dist/services/coder/types.d.ts +64 -8
- package/dist/services/coder/types.d.ts.map +1 -1
- package/dist/services/coder/types.js +36 -1
- package/dist/services/coder/types.js.map +1 -1
- package/dist/services/config.d.ts +1 -0
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +2 -0
- package/dist/services/config.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/keyvalue/service.d.ts +2 -2
- package/dist/services/project/deploy.d.ts +2 -2
- package/dist/services/sandbox/list.d.ts +1 -1
- package/dist/services/sandbox/run.d.ts +4 -4
- package/dist/services/sandbox/types.d.ts +8 -8
- package/dist/services/session/events.d.ts +2 -2
- package/dist/services/stats.d.ts +1 -1
- package/dist/services/stream/service.d.ts +2 -2
- package/dist/services/vector/service.d.ts +2 -2
- package/package.json +2 -2
- package/src/env.d.ts +6 -0
- package/src/services/aigateway/api-reference.ts +167 -0
- package/src/services/aigateway/index.ts +24 -0
- package/src/services/aigateway/service.ts +355 -0
- package/src/services/api-reference.ts +1 -0
- package/src/services/coder/client.ts +10 -0
- package/src/services/coder/skills.ts +19 -0
- package/src/services/coder/types.ts +51 -1
- package/src/services/config.ts +2 -0
- package/src/services/index.ts +1 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
AIGatewayChatCompletionParamsSchema,
|
|
4
|
+
AIGatewayChatCompletionStreamParamsSchema,
|
|
5
|
+
AIGatewayChatCompletionSchema,
|
|
6
|
+
AIGatewayModelsResponseSchema,
|
|
7
|
+
} from './service.ts';
|
|
8
|
+
import type { Service } from '../api-reference.ts';
|
|
9
|
+
|
|
10
|
+
const AIGatewayStreamCompletionSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
choices: z
|
|
13
|
+
.array(
|
|
14
|
+
z
|
|
15
|
+
.object({
|
|
16
|
+
delta: z
|
|
17
|
+
.object({
|
|
18
|
+
role: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Role for the streamed message delta.'),
|
|
22
|
+
content: z.string().optional().describe('Token or text delta.'),
|
|
23
|
+
})
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Incremental assistant message content.'),
|
|
26
|
+
finish_reason: z
|
|
27
|
+
.string()
|
|
28
|
+
.nullable()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Reason the model stopped generating, when available.'),
|
|
31
|
+
})
|
|
32
|
+
.catchall(z.unknown())
|
|
33
|
+
)
|
|
34
|
+
.describe('Streamed completion choices.'),
|
|
35
|
+
})
|
|
36
|
+
.catchall(z.unknown())
|
|
37
|
+
.describe('A single Server-Sent Events data frame for streamed completions.');
|
|
38
|
+
|
|
39
|
+
const service: Service = {
|
|
40
|
+
name: 'AI Gateway',
|
|
41
|
+
slug: 'ai-gateway',
|
|
42
|
+
description: 'List supported LLM models and run routed AI Gateway completions',
|
|
43
|
+
host: 'aigateway',
|
|
44
|
+
hasPublicEndpoints: true,
|
|
45
|
+
endpoints: [
|
|
46
|
+
{
|
|
47
|
+
id: 'list-models',
|
|
48
|
+
title: 'List Models',
|
|
49
|
+
method: 'GET',
|
|
50
|
+
path: '/models',
|
|
51
|
+
description:
|
|
52
|
+
'List model metadata for LLM providers available through AI Gateway, grouped by provider.',
|
|
53
|
+
pathParams: [],
|
|
54
|
+
queryParams: [],
|
|
55
|
+
requestBody: null,
|
|
56
|
+
responseDescription:
|
|
57
|
+
'JSON response with provider keys mapped to arrays of supported model metadata under the `data` envelope.',
|
|
58
|
+
responseFields: { schema: AIGatewayModelsResponseSchema },
|
|
59
|
+
statuses: [
|
|
60
|
+
{ code: 200, description: 'Model catalog returned. Public — no auth required.' },
|
|
61
|
+
],
|
|
62
|
+
examplePath: '/models',
|
|
63
|
+
public: true,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'create-chat-completion',
|
|
67
|
+
title: 'Create Completion',
|
|
68
|
+
method: 'POST',
|
|
69
|
+
path: '/',
|
|
70
|
+
description:
|
|
71
|
+
'Create a completion through the AI Gateway auto-router. The gateway routes by model and request shape, so chat `messages` and legacy `prompt` payloads are both supported.',
|
|
72
|
+
pathParams: [],
|
|
73
|
+
queryParams: [],
|
|
74
|
+
requestBody: {
|
|
75
|
+
description:
|
|
76
|
+
'Completion request. Use `messages` for chat-compatible models and `prompt` for legacy OpenAI completions-compatible models. Additional provider-specific fields are passed through.',
|
|
77
|
+
fields: { schema: AIGatewayChatCompletionParamsSchema },
|
|
78
|
+
},
|
|
79
|
+
responseDescription: 'Provider-compatible completion response.',
|
|
80
|
+
responseHeaders: [
|
|
81
|
+
{
|
|
82
|
+
name: 'X-Gateway-Cost',
|
|
83
|
+
description:
|
|
84
|
+
'Estimated total gateway cost in USD, when billing metadata is available.',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'X-Gateway-Prompt-Tokens',
|
|
88
|
+
description: 'Prompt token count used for gateway billing.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'X-Gateway-Completion-Tokens',
|
|
92
|
+
description: 'Completion token count used for gateway billing.',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
responseFields: { schema: AIGatewayChatCompletionSchema, stripRequired: true },
|
|
96
|
+
statuses: [
|
|
97
|
+
{ code: 200, description: 'Completion created' },
|
|
98
|
+
{ code: 400, description: 'Invalid completion request' },
|
|
99
|
+
{ code: 401, description: 'Unauthorized — invalid or missing API key' },
|
|
100
|
+
{ code: 402, description: 'Payment required — upgrade to a paid plan' },
|
|
101
|
+
],
|
|
102
|
+
examplePath: '/',
|
|
103
|
+
exampleBody: {
|
|
104
|
+
model: 'openai/gpt-4o-mini',
|
|
105
|
+
messages: [{ role: 'user', content: 'Say hello in one sentence.' }],
|
|
106
|
+
max_tokens: 64,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'stream-chat-completion',
|
|
111
|
+
title: 'Stream Completion',
|
|
112
|
+
method: 'POST',
|
|
113
|
+
path: '/',
|
|
114
|
+
description:
|
|
115
|
+
'Create a streaming completion through the AI Gateway auto-router. Set `stream: true` to receive Server-Sent Events token deltas.',
|
|
116
|
+
pathParams: [],
|
|
117
|
+
queryParams: [],
|
|
118
|
+
requestBody: {
|
|
119
|
+
description: 'Completion request with `stream` set to `true`.',
|
|
120
|
+
fields: {
|
|
121
|
+
schema: AIGatewayChatCompletionStreamParamsSchema,
|
|
122
|
+
overrides: { stream: { type: 'true' } },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
responseDescription:
|
|
126
|
+
'Server-Sent Events stream. Each `data:` frame contains a provider-compatible delta payload. The stream ends with `data: [DONE]`.',
|
|
127
|
+
responseHeaders: [
|
|
128
|
+
{
|
|
129
|
+
name: 'Trailer',
|
|
130
|
+
description:
|
|
131
|
+
'Declares billing trailers such as `X-Gateway-Cost`, `X-Gateway-Prompt-Tokens`, and `X-Gateway-Completion-Tokens` for streamed responses.',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'X-Gateway-Cost',
|
|
135
|
+
description:
|
|
136
|
+
'Estimated total gateway cost in USD. For streaming responses this may be delivered as an HTTP trailer after the body completes.',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'X-Gateway-Prompt-Tokens',
|
|
140
|
+
description:
|
|
141
|
+
'Prompt token count used for gateway billing. For streaming responses this may be delivered as an HTTP trailer.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'X-Gateway-Completion-Tokens',
|
|
145
|
+
description:
|
|
146
|
+
'Completion token count used for gateway billing. For streaming responses this may be delivered as an HTTP trailer.',
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
responseFields: { schema: AIGatewayStreamCompletionSchema, stripRequired: true },
|
|
150
|
+
statuses: [
|
|
151
|
+
{ code: 200, description: 'Streaming completion started' },
|
|
152
|
+
{ code: 400, description: 'Invalid completion request' },
|
|
153
|
+
{ code: 401, description: 'Unauthorized — invalid or missing API key' },
|
|
154
|
+
{ code: 402, description: 'Payment required — upgrade to a paid plan' },
|
|
155
|
+
],
|
|
156
|
+
examplePath: '/',
|
|
157
|
+
exampleHeaders: { Accept: 'text/event-stream' },
|
|
158
|
+
exampleBody: {
|
|
159
|
+
model: 'openai/gpt-4o-mini',
|
|
160
|
+
messages: [{ role: 'user', content: 'Count to three.' }],
|
|
161
|
+
stream: true,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export default service;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AIGatewayChatCompletionParamsSchema,
|
|
3
|
+
AIGatewayChatCompletionStreamParamsSchema,
|
|
4
|
+
AIGatewayChatCompletionSchema,
|
|
5
|
+
AIGatewayChatMessageSchema,
|
|
6
|
+
AIGatewayModelProviderSchema,
|
|
7
|
+
AIGatewayModelSchema,
|
|
8
|
+
AIGatewayModelsResponseSchema,
|
|
9
|
+
AIGatewayModelsSchema,
|
|
10
|
+
AIGatewayPricingSchema,
|
|
11
|
+
AIGatewayResponseMetadataSchema,
|
|
12
|
+
AIGatewayService,
|
|
13
|
+
type AIGatewayChatCompletion,
|
|
14
|
+
type AIGatewayChatCompletionParams,
|
|
15
|
+
type AIGatewayChatCompletionStreamParams,
|
|
16
|
+
type AIGatewayChatMessage,
|
|
17
|
+
type AIGatewayModel,
|
|
18
|
+
type AIGatewayModelProvider,
|
|
19
|
+
type AIGatewayModels,
|
|
20
|
+
type AIGatewayModelsResponse,
|
|
21
|
+
type AIGatewayPricing,
|
|
22
|
+
type AIGatewayResponseMetadata,
|
|
23
|
+
type AIGatewayStreamingCompletion,
|
|
24
|
+
} from './service.ts';
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { StructuredError } from '../../error.ts';
|
|
3
|
+
import { FetchAdapter } from '../adapter.ts';
|
|
4
|
+
import { buildUrl, toServiceException, toPayload } from '../_util.ts';
|
|
5
|
+
|
|
6
|
+
const AIGatewayModelsResponseError = StructuredError('AIGatewayModelsResponseError')<{
|
|
7
|
+
error?: string;
|
|
8
|
+
message?: string;
|
|
9
|
+
}>();
|
|
10
|
+
|
|
11
|
+
export const AIGatewayPricingSchema = z.object({
|
|
12
|
+
input: z.number().describe('Input token price.'),
|
|
13
|
+
output: z.number().describe('Output token price.'),
|
|
14
|
+
cached_input: z.number().optional().describe('Cached input token price.'),
|
|
15
|
+
unit: z.string().describe('Pricing unit.'),
|
|
16
|
+
currency: z.string().describe('Pricing currency.'),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type AIGatewayPricing = z.infer<typeof AIGatewayPricingSchema>;
|
|
20
|
+
|
|
21
|
+
export const AIGatewayModelProviderSchema = z.object({
|
|
22
|
+
env: z.array(z.string()).optional().describe('Environment variables used by this provider.'),
|
|
23
|
+
api: z.string().optional().describe('Provider API URL.'),
|
|
24
|
+
doc: z.string().optional().describe('Provider documentation URL.'),
|
|
25
|
+
logo_url: z.string().optional().describe('Provider logo URL.'),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type AIGatewayModelProvider = z.infer<typeof AIGatewayModelProviderSchema>;
|
|
29
|
+
|
|
30
|
+
export const AIGatewayModelSchema = z.object({
|
|
31
|
+
id: z.string().describe('Model identifier.'),
|
|
32
|
+
name: z.string().describe('Display name.'),
|
|
33
|
+
created: z.number().optional().describe('Unix timestamp when the model was created.'),
|
|
34
|
+
api: z.string().optional().describe('Compatible provider API shape.'),
|
|
35
|
+
family: z.string().optional().describe('Model family.'),
|
|
36
|
+
context_window: z.number().optional().describe('Maximum context window.'),
|
|
37
|
+
max_output_tokens: z.number().optional().describe('Maximum output token count.'),
|
|
38
|
+
input_modalities: z.array(z.string()).optional().describe('Supported input modalities.'),
|
|
39
|
+
output_modalities: z.array(z.string()).optional().describe('Supported output modalities.'),
|
|
40
|
+
attachment: z.boolean().optional().describe('Whether the model supports attachments.'),
|
|
41
|
+
reasoning: z.boolean().optional().describe('Whether the model supports reasoning.'),
|
|
42
|
+
tool_call: z.boolean().optional().describe('Whether the model supports tool calls.'),
|
|
43
|
+
temperature: z.boolean().optional().describe('Whether the model supports temperature.'),
|
|
44
|
+
knowledge: z.string().optional().describe('Knowledge cutoff or label.'),
|
|
45
|
+
open_weights: z.boolean().optional().describe('Whether the model has open weights.'),
|
|
46
|
+
provider: AIGatewayModelProviderSchema.optional().describe('Provider metadata.'),
|
|
47
|
+
pricing: AIGatewayPricingSchema.optional().describe('Model pricing.'),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export type AIGatewayModel = z.infer<typeof AIGatewayModelSchema>;
|
|
51
|
+
|
|
52
|
+
export const AIGatewayModelsSchema = z.record(z.string(), z.array(AIGatewayModelSchema));
|
|
53
|
+
export type AIGatewayModels = z.infer<typeof AIGatewayModelsSchema>;
|
|
54
|
+
|
|
55
|
+
export const AIGatewayModelsResponseSchema = z.object({
|
|
56
|
+
success: z.boolean(),
|
|
57
|
+
data: AIGatewayModelsSchema.optional(),
|
|
58
|
+
message: z.string().optional(),
|
|
59
|
+
error: z.string().optional(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export type AIGatewayModelsResponse = z.infer<typeof AIGatewayModelsResponseSchema>;
|
|
63
|
+
|
|
64
|
+
export const AIGatewayChatMessageSchema = z.object({
|
|
65
|
+
role: z.enum(['system', 'developer', 'user', 'assistant', 'tool']),
|
|
66
|
+
content: z
|
|
67
|
+
.union([
|
|
68
|
+
z.string(),
|
|
69
|
+
z.array(
|
|
70
|
+
z
|
|
71
|
+
.object({
|
|
72
|
+
type: z.string(),
|
|
73
|
+
})
|
|
74
|
+
.catchall(z.unknown())
|
|
75
|
+
),
|
|
76
|
+
z.null(),
|
|
77
|
+
])
|
|
78
|
+
.optional(),
|
|
79
|
+
name: z.string().optional(),
|
|
80
|
+
tool_call_id: z.string().optional(),
|
|
81
|
+
tool_calls: z.array(z.unknown()).optional(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export type AIGatewayChatMessage = z.infer<typeof AIGatewayChatMessageSchema>;
|
|
85
|
+
|
|
86
|
+
const missingCompletionInputMessage = 'either prompt or messages must be provided';
|
|
87
|
+
|
|
88
|
+
function hasCompletionInput(params: { prompt?: string | string[]; messages?: unknown[] }): boolean {
|
|
89
|
+
if (params.messages && params.messages.length > 0) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (typeof params.prompt === 'string') {
|
|
93
|
+
return params.prompt.trim().length > 0;
|
|
94
|
+
}
|
|
95
|
+
if (Array.isArray(params.prompt)) {
|
|
96
|
+
return params.prompt.length > 0 && params.prompt.every((item) => item.trim().length > 0);
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const AIGatewayChatCompletionParamsSchema = z
|
|
102
|
+
.object({
|
|
103
|
+
model: z.string().describe('Model to use for the completion.'),
|
|
104
|
+
messages: z.array(AIGatewayChatMessageSchema).optional().describe('Messages to complete.'),
|
|
105
|
+
prompt: z
|
|
106
|
+
.union([z.string(), z.array(z.string())])
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('Prompt to complete.'),
|
|
109
|
+
temperature: z.number().optional(),
|
|
110
|
+
top_p: z.number().optional(),
|
|
111
|
+
max_tokens: z.number().optional(),
|
|
112
|
+
stream: z.boolean().optional(),
|
|
113
|
+
stop: z.union([z.string(), z.array(z.string())]).optional(),
|
|
114
|
+
})
|
|
115
|
+
.catchall(z.unknown())
|
|
116
|
+
.superRefine((params, ctx) => {
|
|
117
|
+
if (!hasCompletionInput(params)) {
|
|
118
|
+
ctx.addIssue({
|
|
119
|
+
code: 'custom',
|
|
120
|
+
message: missingCompletionInputMessage,
|
|
121
|
+
path: ['messages'],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export type AIGatewayChatCompletionParams = z.infer<typeof AIGatewayChatCompletionParamsSchema>;
|
|
127
|
+
|
|
128
|
+
export const AIGatewayChatCompletionStreamParamsSchema =
|
|
129
|
+
AIGatewayChatCompletionParamsSchema.safeExtend({
|
|
130
|
+
stream: z.literal(true).describe('Enable Server-Sent Events streaming.'),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export type AIGatewayChatCompletionStreamParams = z.infer<
|
|
134
|
+
typeof AIGatewayChatCompletionStreamParamsSchema
|
|
135
|
+
>;
|
|
136
|
+
|
|
137
|
+
export const AIGatewayChatCompletionSchema = z
|
|
138
|
+
.object({
|
|
139
|
+
id: z.string().optional(),
|
|
140
|
+
object: z.string().optional(),
|
|
141
|
+
created: z.number().optional(),
|
|
142
|
+
model: z.string().optional(),
|
|
143
|
+
choices: z.array(z.unknown()).optional(),
|
|
144
|
+
usage: z.unknown().optional(),
|
|
145
|
+
agentuity: z
|
|
146
|
+
.object({
|
|
147
|
+
headers: z
|
|
148
|
+
.record(z.string(), z.string())
|
|
149
|
+
.optional()
|
|
150
|
+
.describe('AI Gateway response headers captured from the HTTP response.'),
|
|
151
|
+
cost: z
|
|
152
|
+
.object({
|
|
153
|
+
total: z.number().optional().describe('Total estimated gateway cost in USD.'),
|
|
154
|
+
promptTokens: z
|
|
155
|
+
.number()
|
|
156
|
+
.optional()
|
|
157
|
+
.describe('Prompt token count used for gateway billing.'),
|
|
158
|
+
completionTokens: z
|
|
159
|
+
.number()
|
|
160
|
+
.optional()
|
|
161
|
+
.describe('Completion token count used for gateway billing.'),
|
|
162
|
+
})
|
|
163
|
+
.optional()
|
|
164
|
+
.describe('Parsed AI Gateway cost information when available.'),
|
|
165
|
+
})
|
|
166
|
+
.optional()
|
|
167
|
+
.describe('Agentuity AI Gateway metadata.'),
|
|
168
|
+
})
|
|
169
|
+
.catchall(z.unknown());
|
|
170
|
+
|
|
171
|
+
export type AIGatewayChatCompletion = z.infer<typeof AIGatewayChatCompletionSchema>;
|
|
172
|
+
|
|
173
|
+
export const AIGatewayResponseMetadataSchema = z.object({
|
|
174
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
175
|
+
cost: z
|
|
176
|
+
.object({
|
|
177
|
+
total: z.number().optional(),
|
|
178
|
+
promptTokens: z.number().optional(),
|
|
179
|
+
completionTokens: z.number().optional(),
|
|
180
|
+
})
|
|
181
|
+
.optional(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export type AIGatewayResponseMetadata = z.infer<typeof AIGatewayResponseMetadataSchema>;
|
|
185
|
+
|
|
186
|
+
export type AIGatewayStreamingCompletion = {
|
|
187
|
+
stream: ReadableStream<Uint8Array>;
|
|
188
|
+
metadata: Promise<AIGatewayResponseMetadata>;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
function parseNumber(value: string | undefined): number | undefined {
|
|
192
|
+
if (value === undefined || value.trim() === '') {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
const parsed = Number(value);
|
|
196
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function extractGatewayMetadataFromHeaders(headers: Headers): AIGatewayResponseMetadata {
|
|
200
|
+
const captured: Record<string, string> = {};
|
|
201
|
+
for (const [key, value] of headers.entries()) {
|
|
202
|
+
const lower = key.toLowerCase();
|
|
203
|
+
if (
|
|
204
|
+
lower.startsWith('x-gateway-') ||
|
|
205
|
+
(lower.startsWith('x-agentuity-') &&
|
|
206
|
+
(lower.includes('cost') || lower.includes('token') || lower.includes('usage')))
|
|
207
|
+
) {
|
|
208
|
+
captured[lower] = value;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const total = parseNumber(captured['x-gateway-cost']);
|
|
213
|
+
const promptTokens = parseNumber(captured['x-gateway-prompt-tokens']);
|
|
214
|
+
const completionTokens = parseNumber(captured['x-gateway-completion-tokens']);
|
|
215
|
+
const cost =
|
|
216
|
+
total !== undefined || promptTokens !== undefined || completionTokens !== undefined
|
|
217
|
+
? { total, promptTokens, completionTokens }
|
|
218
|
+
: undefined;
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
...(Object.keys(captured).length > 0 ? { headers: captured } : {}),
|
|
222
|
+
...(cost ? { cost } : {}),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function extractGatewayMetadata(response: Response): Promise<AIGatewayResponseMetadata> {
|
|
227
|
+
const metadata = extractGatewayMetadataFromHeaders(response.headers);
|
|
228
|
+
const trailers = (response as Response & { trailers?: Promise<Headers> }).trailers;
|
|
229
|
+
if (trailers) {
|
|
230
|
+
try {
|
|
231
|
+
const trailerMetadata = extractGatewayMetadataFromHeaders(await trailers);
|
|
232
|
+
const cost =
|
|
233
|
+
metadata.cost || trailerMetadata.cost
|
|
234
|
+
? { ...(metadata.cost ?? {}), ...(trailerMetadata.cost ?? {}) }
|
|
235
|
+
: undefined;
|
|
236
|
+
return {
|
|
237
|
+
headers: { ...metadata.headers, ...trailerMetadata.headers },
|
|
238
|
+
...(cost ? { cost } : {}),
|
|
239
|
+
};
|
|
240
|
+
} catch {
|
|
241
|
+
// Some runtimes expose a trailers promise but reject when trailers are unavailable.
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return metadata;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function attachGatewayMetadata<T extends Record<string, unknown>>(
|
|
248
|
+
payload: T,
|
|
249
|
+
metadata: AIGatewayResponseMetadata
|
|
250
|
+
): T {
|
|
251
|
+
if (!metadata.headers && !metadata.cost) {
|
|
252
|
+
return payload;
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
...payload,
|
|
256
|
+
agentuity: {
|
|
257
|
+
...(typeof payload.agentuity === 'object' && payload.agentuity !== null
|
|
258
|
+
? payload.agentuity
|
|
259
|
+
: {}),
|
|
260
|
+
...metadata,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export class AIGatewayService {
|
|
266
|
+
constructor(
|
|
267
|
+
readonly baseUrl: string,
|
|
268
|
+
readonly adapter: FetchAdapter
|
|
269
|
+
) {}
|
|
270
|
+
|
|
271
|
+
async listModels(): Promise<AIGatewayModels> {
|
|
272
|
+
const method = 'GET';
|
|
273
|
+
const url = buildUrl(this.baseUrl, '/models');
|
|
274
|
+
const response = await this.adapter.invoke<AIGatewayModelsResponse>(url, {
|
|
275
|
+
method,
|
|
276
|
+
telemetry: { name: 'aigateway.models.list' },
|
|
277
|
+
});
|
|
278
|
+
if (!response.ok) {
|
|
279
|
+
throw await toServiceException(method, url, response.response);
|
|
280
|
+
}
|
|
281
|
+
const payload = AIGatewayModelsResponseSchema.parse(response.data);
|
|
282
|
+
if (!payload.success) {
|
|
283
|
+
throw new AIGatewayModelsResponseError({
|
|
284
|
+
message: payload.error || payload.message || 'AI Gateway failed to list models',
|
|
285
|
+
error: payload.error,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (!payload.data) {
|
|
289
|
+
throw new AIGatewayModelsResponseError({
|
|
290
|
+
message: 'AI Gateway model response did not include data',
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
return payload.data;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async complete(params: AIGatewayChatCompletionParams): Promise<AIGatewayChatCompletion> {
|
|
297
|
+
const method = 'POST';
|
|
298
|
+
const url = buildUrl(this.baseUrl, '/');
|
|
299
|
+
const [body, contentType] = await toPayload(
|
|
300
|
+
AIGatewayChatCompletionParamsSchema.parse(params)
|
|
301
|
+
);
|
|
302
|
+
const response = await this.adapter.invoke<AIGatewayChatCompletion>(url, {
|
|
303
|
+
method,
|
|
304
|
+
body,
|
|
305
|
+
contentType,
|
|
306
|
+
telemetry: { name: 'aigateway.completions.create' },
|
|
307
|
+
});
|
|
308
|
+
if (!response.ok) {
|
|
309
|
+
throw await toServiceException(method, url, response.response);
|
|
310
|
+
}
|
|
311
|
+
const payload = attachGatewayMetadata(
|
|
312
|
+
response.data as Record<string, unknown>,
|
|
313
|
+
await extractGatewayMetadata(response.response)
|
|
314
|
+
);
|
|
315
|
+
return AIGatewayChatCompletionSchema.parse(payload);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async streamComplete(
|
|
319
|
+
params: AIGatewayChatCompletionParams
|
|
320
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
321
|
+
return (await this.streamCompleteWithMetadata(params)).stream;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async streamCompleteWithMetadata(
|
|
325
|
+
params: AIGatewayChatCompletionParams
|
|
326
|
+
): Promise<AIGatewayStreamingCompletion> {
|
|
327
|
+
const method = 'POST';
|
|
328
|
+
const url = buildUrl(this.baseUrl, '/');
|
|
329
|
+
const [body, contentType] = await toPayload(
|
|
330
|
+
AIGatewayChatCompletionParamsSchema.parse({ ...params, stream: true })
|
|
331
|
+
);
|
|
332
|
+
const response = await this.adapter.invoke<never>(url, {
|
|
333
|
+
method,
|
|
334
|
+
body,
|
|
335
|
+
contentType,
|
|
336
|
+
headers: { Accept: 'text/event-stream' },
|
|
337
|
+
binary: true,
|
|
338
|
+
telemetry: { name: 'aigateway.completions.stream' },
|
|
339
|
+
});
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
throw await toServiceException(method, url, response.response);
|
|
342
|
+
}
|
|
343
|
+
if (!response.response.body) {
|
|
344
|
+
throw await toServiceException(
|
|
345
|
+
method,
|
|
346
|
+
url,
|
|
347
|
+
new Response('Streaming response did not include a body', { status: 502 })
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
stream: response.response.body,
|
|
352
|
+
metadata: extractGatewayMetadata(response.response),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
coderUpdateCustomAgent,
|
|
33
33
|
} from './agents.ts';
|
|
34
34
|
import {
|
|
35
|
+
coderCreateCustomSkill,
|
|
35
36
|
coderCreateSkillBucket,
|
|
36
37
|
coderDeleteSavedSkill,
|
|
37
38
|
coderDeleteSkillBucket,
|
|
@@ -79,6 +80,7 @@ import type {
|
|
|
79
80
|
CoderSkillBucket,
|
|
80
81
|
CoderSkillBucketListResponse,
|
|
81
82
|
CoderCreateSkillBucketRequest,
|
|
83
|
+
CoderCreateCustomSkillRequest,
|
|
82
84
|
CoderUpdateCustomAgentRequest,
|
|
83
85
|
CoderCreateWorkspaceRequest,
|
|
84
86
|
CoderUpdateWorkspaceRequest,
|
|
@@ -449,6 +451,14 @@ export class CoderClient {
|
|
|
449
451
|
return coderSaveSkill(client, { body });
|
|
450
452
|
}
|
|
451
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Creates a custom SKILL.md-backed skill in the caller's library.
|
|
456
|
+
*/
|
|
457
|
+
async createCustomSkill(body: CoderCreateCustomSkillRequest): Promise<CoderSavedSkill> {
|
|
458
|
+
const client = await this.#getClient();
|
|
459
|
+
return coderCreateCustomSkill(client, { body });
|
|
460
|
+
}
|
|
461
|
+
|
|
452
462
|
/**
|
|
453
463
|
* Deletes a saved skill from the caller's library.
|
|
454
464
|
*/
|
|
@@ -2,12 +2,14 @@ import { z } from 'zod/v4';
|
|
|
2
2
|
import { type APIClient } from '../api.ts';
|
|
3
3
|
import {
|
|
4
4
|
CoderCreateSkillBucketRequestSchema,
|
|
5
|
+
CoderCreateCustomSkillRequestSchema,
|
|
5
6
|
CoderSavedSkillListResponseSchema,
|
|
6
7
|
CoderSavedSkillSchema,
|
|
7
8
|
CoderSaveSkillRequestSchema,
|
|
8
9
|
CoderSkillBucketListResponseSchema,
|
|
9
10
|
CoderSkillBucketSchema,
|
|
10
11
|
type CoderCreateSkillBucketRequest,
|
|
12
|
+
type CoderCreateCustomSkillRequest,
|
|
11
13
|
type CoderSavedSkill,
|
|
12
14
|
type CoderSavedSkillListResponse,
|
|
13
15
|
type CoderSaveSkillRequest,
|
|
@@ -59,6 +61,23 @@ export async function coderSaveSkill(
|
|
|
59
61
|
return resp.skill;
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
export async function coderCreateCustomSkill(
|
|
65
|
+
client: APIClient,
|
|
66
|
+
params: { body: CoderCreateCustomSkillRequest }
|
|
67
|
+
): Promise<CoderSavedSkill> {
|
|
68
|
+
const body = CoderCreateCustomSkillRequestSchema.parse(params.body);
|
|
69
|
+
return coderSaveSkill(client, {
|
|
70
|
+
body: {
|
|
71
|
+
source: 'custom',
|
|
72
|
+
repo: 'custom',
|
|
73
|
+
skillId: body.skillId,
|
|
74
|
+
name: body.name,
|
|
75
|
+
...(body.description !== undefined ? { description: body.description } : {}),
|
|
76
|
+
content: body.content,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
62
81
|
export async function coderDeleteSavedSkill(
|
|
63
82
|
client: APIClient,
|
|
64
83
|
params: { skillId: string }
|