@bolt-foundry/gambit 0.8.0 → 0.8.3

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 (61) hide show
  1. package/CHANGELOG.md +82 -2
  2. package/README.md +31 -9
  3. package/esm/gambit/simulator-ui/dist/bundle.js +4744 -4360
  4. package/esm/gambit/simulator-ui/dist/bundle.js.map +4 -4
  5. package/esm/gambit/simulator-ui/dist/favicon.ico +0 -0
  6. package/esm/mod.d.ts +7 -3
  7. package/esm/mod.d.ts.map +1 -1
  8. package/esm/mod.js +5 -1
  9. package/esm/src/cli_utils.d.ts +3 -2
  10. package/esm/src/cli_utils.d.ts.map +1 -1
  11. package/esm/src/cli_utils.js +43 -27
  12. package/esm/src/openai_compat.d.ts +63 -0
  13. package/esm/src/openai_compat.d.ts.map +1 -0
  14. package/esm/src/openai_compat.js +277 -0
  15. package/esm/src/providers/google.d.ts +16 -0
  16. package/esm/src/providers/google.d.ts.map +1 -0
  17. package/esm/src/providers/google.js +352 -0
  18. package/esm/src/providers/ollama.d.ts +17 -0
  19. package/esm/src/providers/ollama.d.ts.map +1 -0
  20. package/esm/src/providers/ollama.js +509 -0
  21. package/esm/src/providers/openrouter.d.ts +14 -1
  22. package/esm/src/providers/openrouter.d.ts.map +1 -1
  23. package/esm/src/providers/openrouter.js +460 -463
  24. package/esm/src/server.d.ts +4 -0
  25. package/esm/src/server.d.ts.map +1 -1
  26. package/esm/src/server.js +623 -164
  27. package/esm/src/trace.d.ts.map +1 -1
  28. package/esm/src/trace.js +3 -6
  29. package/package.json +2 -2
  30. package/script/gambit/simulator-ui/dist/bundle.js +4744 -4360
  31. package/script/gambit/simulator-ui/dist/bundle.js.map +4 -4
  32. package/script/gambit/simulator-ui/dist/favicon.ico +0 -0
  33. package/script/mod.d.ts +7 -3
  34. package/script/mod.d.ts.map +1 -1
  35. package/script/mod.js +9 -3
  36. package/script/src/cli_utils.d.ts +3 -2
  37. package/script/src/cli_utils.d.ts.map +1 -1
  38. package/script/src/cli_utils.js +42 -26
  39. package/script/src/openai_compat.d.ts +63 -0
  40. package/script/src/openai_compat.d.ts.map +1 -0
  41. package/script/src/openai_compat.js +281 -0
  42. package/script/src/providers/google.d.ts +16 -0
  43. package/script/src/providers/google.d.ts.map +1 -0
  44. package/script/src/providers/google.js +359 -0
  45. package/script/src/providers/ollama.d.ts +17 -0
  46. package/script/src/providers/ollama.d.ts.map +1 -0
  47. package/script/src/providers/ollama.js +551 -0
  48. package/script/src/providers/openrouter.d.ts +14 -1
  49. package/script/src/providers/openrouter.d.ts.map +1 -1
  50. package/script/src/providers/openrouter.js +461 -463
  51. package/script/src/server.d.ts +4 -0
  52. package/script/src/server.d.ts.map +1 -1
  53. package/script/src/server.js +623 -164
  54. package/script/src/trace.d.ts.map +1 -1
  55. package/script/src/trace.js +3 -6
  56. package/esm/src/compat/openai.d.ts +0 -2
  57. package/esm/src/compat/openai.d.ts.map +0 -1
  58. package/esm/src/compat/openai.js +0 -1
  59. package/script/src/compat/openai.d.ts +0 -2
  60. package/script/src/compat/openai.d.ts.map +0 -1
  61. package/script/src/compat/openai.js +0 -5
@@ -0,0 +1,352 @@
1
+ import OpenAI from "openai";
2
+ export const GOOGLE_PREFIX = "google/";
3
+ const DEFAULT_GOOGLE_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/";
4
+ function safeJson(input) {
5
+ try {
6
+ const parsed = JSON.parse(input);
7
+ if (parsed && typeof parsed === "object") {
8
+ return parsed;
9
+ }
10
+ }
11
+ catch {
12
+ // fall through
13
+ }
14
+ return {};
15
+ }
16
+ function extractContent(content) {
17
+ if (typeof content === "string")
18
+ return content;
19
+ if (Array.isArray(content)) {
20
+ return content
21
+ .map((part) => (typeof part === "string" ? part : part.text ?? ""))
22
+ .join("");
23
+ }
24
+ return "";
25
+ }
26
+ function normalizeMessage(content) {
27
+ const toolCalls = content.tool_calls ??
28
+ undefined;
29
+ return {
30
+ role: content.role,
31
+ content: extractContent(content.content),
32
+ name: content.name,
33
+ tool_call_id: content.tool_call_id,
34
+ tool_calls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined,
35
+ };
36
+ }
37
+ function toToolChoice(choice) {
38
+ if (!choice)
39
+ return undefined;
40
+ if (choice === "auto" || choice === "required")
41
+ return choice;
42
+ return { type: "function", function: { name: choice.function.name } };
43
+ }
44
+ function responseItemsToChatMessages(items, instructions) {
45
+ const messages = [];
46
+ if (instructions) {
47
+ messages.push({ role: "system", content: instructions });
48
+ }
49
+ for (const item of items) {
50
+ if (item.type === "message") {
51
+ const content = item.content
52
+ .map((part) => part.text)
53
+ .join("");
54
+ messages.push({ role: item.role, content });
55
+ continue;
56
+ }
57
+ if (item.type === "function_call") {
58
+ messages.push({
59
+ role: "assistant",
60
+ content: null,
61
+ tool_calls: [{
62
+ id: item.call_id,
63
+ type: "function",
64
+ function: { name: item.name, arguments: item.arguments },
65
+ }],
66
+ });
67
+ continue;
68
+ }
69
+ if (item.type === "function_call_output") {
70
+ messages.push({
71
+ role: "tool",
72
+ content: item.output,
73
+ tool_call_id: item.call_id,
74
+ });
75
+ }
76
+ }
77
+ return messages;
78
+ }
79
+ function responseItemsFromChatMessage(message, toolCalls) {
80
+ const output = [];
81
+ if (typeof message.content === "string" && message.content.length > 0) {
82
+ output.push({
83
+ type: "message",
84
+ role: "assistant",
85
+ content: [{ type: "output_text", text: message.content }],
86
+ });
87
+ }
88
+ if (toolCalls) {
89
+ for (const call of toolCalls) {
90
+ output.push({
91
+ type: "function_call",
92
+ call_id: call.id,
93
+ name: call.name,
94
+ arguments: JSON.stringify(call.args),
95
+ });
96
+ }
97
+ }
98
+ else if (message.tool_calls) {
99
+ for (const call of message.tool_calls) {
100
+ output.push({
101
+ type: "function_call",
102
+ call_id: call.id,
103
+ name: call.function.name,
104
+ arguments: call.function.arguments,
105
+ });
106
+ }
107
+ }
108
+ return output;
109
+ }
110
+ function mapChatUsage(usage) {
111
+ if (!usage)
112
+ return undefined;
113
+ return {
114
+ promptTokens: usage.prompt_tokens ?? 0,
115
+ completionTokens: usage.completion_tokens ?? 0,
116
+ totalTokens: usage.total_tokens ?? 0,
117
+ };
118
+ }
119
+ export function createGoogleProvider(opts) {
120
+ const client = (opts.client ??
121
+ new OpenAI({
122
+ apiKey: opts.apiKey,
123
+ baseURL: opts.baseURL ?? DEFAULT_GOOGLE_BASE_URL,
124
+ }));
125
+ return {
126
+ async responses(input) {
127
+ const request = input.request;
128
+ const params = { ...(request.params ?? {}) };
129
+ if (request.max_output_tokens !== undefined &&
130
+ params.max_tokens === undefined) {
131
+ params.max_tokens = request.max_output_tokens;
132
+ }
133
+ const messages = responseItemsToChatMessages(request.input, request.instructions);
134
+ const toolChoice = toToolChoice(request.tool_choice);
135
+ if (request.stream) {
136
+ const stream = await client.chat.completions.create({
137
+ model: request.model,
138
+ messages: messages,
139
+ tools: request.tools,
140
+ tool_choice: toolChoice ?? "auto",
141
+ stream: true,
142
+ ...params,
143
+ });
144
+ const contentParts = [];
145
+ const toolCallMap = new Map();
146
+ let responseId;
147
+ let created;
148
+ for await (const chunk of stream) {
149
+ responseId = responseId ?? chunk.id;
150
+ created = created ?? chunk.created;
151
+ const choice = chunk.choices[0];
152
+ const delta = choice.delta;
153
+ if (typeof delta.content === "string") {
154
+ contentParts.push(delta.content);
155
+ input.onStreamEvent?.({
156
+ type: "response.output_text.delta",
157
+ output_index: 0,
158
+ delta: delta.content,
159
+ });
160
+ }
161
+ else if (Array.isArray(delta.content)) {
162
+ const text = delta.content
163
+ .map((part) => (typeof part === "string" ? part : part.text ?? ""))
164
+ .join("");
165
+ if (text) {
166
+ contentParts.push(text);
167
+ input.onStreamEvent?.({
168
+ type: "response.output_text.delta",
169
+ output_index: 0,
170
+ delta: text,
171
+ });
172
+ }
173
+ }
174
+ for (const tc of delta.tool_calls ?? []) {
175
+ const idx = tc.index ?? 0;
176
+ const existing = toolCallMap.get(idx) ??
177
+ {
178
+ id: tc.id,
179
+ function: { name: tc.function?.name, arguments: "" },
180
+ };
181
+ if (tc.id)
182
+ existing.id = tc.id;
183
+ if (tc.function?.name)
184
+ existing.function.name = tc.function.name;
185
+ if (tc.function?.arguments) {
186
+ existing.function.arguments += tc.function.arguments;
187
+ }
188
+ toolCallMap.set(idx, existing);
189
+ }
190
+ }
191
+ const tool_calls = Array.from(toolCallMap.values()).map((tc) => ({
192
+ id: tc.id ?? crypto.randomUUID().replace(/-/g, "").slice(0, 24),
193
+ type: "function",
194
+ function: {
195
+ name: tc.function.name ?? "",
196
+ arguments: tc.function.arguments,
197
+ },
198
+ }));
199
+ const message = normalizeMessage({
200
+ role: "assistant",
201
+ content: contentParts.length ? contentParts.join("") : null,
202
+ tool_calls,
203
+ });
204
+ const toolCalls = tool_calls.length > 0
205
+ ? tool_calls.map((tc) => ({
206
+ id: tc.id,
207
+ name: tc.function.name,
208
+ args: safeJson(tc.function.arguments),
209
+ }))
210
+ : undefined;
211
+ const output = responseItemsFromChatMessage(message, toolCalls);
212
+ const response = {
213
+ id: responseId ?? crypto.randomUUID(),
214
+ object: "response",
215
+ model: request.model,
216
+ created,
217
+ status: "completed",
218
+ output,
219
+ };
220
+ input.onStreamEvent?.({ type: "response.completed", response });
221
+ return response;
222
+ }
223
+ const response = await client.chat.completions.create({
224
+ model: request.model,
225
+ messages: messages,
226
+ tools: request.tools,
227
+ tool_choice: toolChoice ?? "auto",
228
+ stream: false,
229
+ ...params,
230
+ });
231
+ const choice = response.choices[0];
232
+ const normalizedMessage = normalizeMessage(choice.message);
233
+ const toolCalls = choice.message.tool_calls?.map((tc) => ({
234
+ id: tc.id,
235
+ name: tc.function.name,
236
+ args: safeJson(tc.function.arguments),
237
+ }));
238
+ return {
239
+ id: response.id,
240
+ object: "response",
241
+ model: response.model,
242
+ created: response.created,
243
+ status: "completed",
244
+ output: responseItemsFromChatMessage(normalizedMessage, toolCalls),
245
+ usage: mapChatUsage(response.usage),
246
+ };
247
+ },
248
+ async chat(input) {
249
+ const params = input.params ?? {};
250
+ if (input.stream) {
251
+ const stream = await client.chat.completions.create({
252
+ model: input.model,
253
+ messages: input.messages,
254
+ tools: input
255
+ .tools,
256
+ tool_choice: "auto",
257
+ stream: true,
258
+ ...params,
259
+ });
260
+ let finishReason = null;
261
+ const contentParts = [];
262
+ const toolCallMap = new Map();
263
+ for await (const chunk of stream) {
264
+ const choice = chunk.choices[0];
265
+ const fr = choice.finish_reason;
266
+ if (fr === "stop" || fr === "tool_calls" || fr === "length" ||
267
+ fr === null) {
268
+ finishReason = fr ?? finishReason;
269
+ }
270
+ const delta = choice.delta;
271
+ if (typeof delta.content === "string") {
272
+ contentParts.push(delta.content);
273
+ input.onStreamText?.(delta.content);
274
+ }
275
+ else if (Array.isArray(delta.content)) {
276
+ const chunkStr = delta.content
277
+ .map((c) => (typeof c === "string" ? c : c.text ?? ""))
278
+ .join("");
279
+ if (chunkStr) {
280
+ contentParts.push(chunkStr);
281
+ input.onStreamText?.(chunkStr);
282
+ }
283
+ }
284
+ for (const tc of delta.tool_calls ?? []) {
285
+ const idx = tc.index ?? 0;
286
+ const existing = toolCallMap.get(idx) ??
287
+ {
288
+ id: tc.id,
289
+ function: { name: tc.function?.name, arguments: "" },
290
+ };
291
+ if (tc.id)
292
+ existing.id = tc.id;
293
+ if (tc.function?.name)
294
+ existing.function.name = tc.function.name;
295
+ if (tc.function?.arguments) {
296
+ existing.function.arguments += tc.function.arguments;
297
+ }
298
+ toolCallMap.set(idx, existing);
299
+ }
300
+ }
301
+ const tool_calls = Array.from(toolCallMap.values()).map((tc) => ({
302
+ id: tc.id ?? crypto.randomUUID().replace(/-/g, "").slice(0, 24),
303
+ type: "function",
304
+ function: {
305
+ name: tc.function.name ?? "",
306
+ arguments: tc.function.arguments,
307
+ },
308
+ }));
309
+ const message = normalizeMessage({
310
+ role: "assistant",
311
+ content: contentParts.length ? contentParts.join("") : null,
312
+ tool_calls,
313
+ });
314
+ const toolCalls = tool_calls.length > 0
315
+ ? tool_calls.map((tc) => ({
316
+ id: tc.id,
317
+ name: tc.function.name,
318
+ args: safeJson(tc.function.arguments),
319
+ }))
320
+ : undefined;
321
+ return {
322
+ message,
323
+ finishReason: finishReason ?? "stop",
324
+ toolCalls,
325
+ };
326
+ }
327
+ const response = await client.chat.completions.create({
328
+ model: input.model,
329
+ messages: input
330
+ .messages,
331
+ tools: input
332
+ .tools,
333
+ tool_choice: "auto",
334
+ stream: false,
335
+ ...params,
336
+ });
337
+ const choice = response.choices[0];
338
+ const message = normalizeMessage(choice.message);
339
+ const toolCalls = choice.message.tool_calls?.map((tc) => ({
340
+ id: tc.id,
341
+ name: tc.function.name,
342
+ args: safeJson(tc.function.arguments),
343
+ }));
344
+ return {
345
+ message,
346
+ finishReason: (choice.finish_reason ?? "stop"),
347
+ toolCalls,
348
+ usage: mapChatUsage(response.usage),
349
+ };
350
+ },
351
+ };
352
+ }
@@ -0,0 +1,17 @@
1
+ import type { ModelProvider } from "@bolt-foundry/gambit-core";
2
+ export declare const OLLAMA_PREFIX = "ollama/";
3
+ export declare const DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434/v1";
4
+ type OpenAIClient = {
5
+ responses: {
6
+ create: (params: unknown) => Promise<unknown>;
7
+ };
8
+ };
9
+ export declare function fetchOllamaTags(baseURL: string | undefined): Promise<Set<string>>;
10
+ export declare function ensureOllamaModel(model: string, baseURL: string | undefined): Promise<void>;
11
+ export declare function createOllamaProvider(opts: {
12
+ apiKey?: string;
13
+ baseURL?: string;
14
+ client?: OpenAIClient;
15
+ }): ModelProvider;
16
+ export {};
17
+ //# sourceMappingURL=ollama.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.d.ts","sourceRoot":"","sources":["../../../src/src/providers/ollama.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAKV,aAAa,EAOd,MAAM,2BAA2B,CAAC;AAOnC,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,uBAAuB,8BAA8B,CAAC;AAEnE,KAAK,YAAY,GAAG;IAClB,SAAS,EAAE;QACT,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;KAC/C,CAAC;CACH,CAAC;AAeF,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAgBtB;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,OAAO,CAAC,IAAI,CAAC,CA8Cf;AAodD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,GAAG,aAAa,CAoChB"}