@sudobility/shapeshyft_engine 1.0.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.
Files changed (83) hide show
  1. package/CLAUDE.md +27 -0
  2. package/README.md +11 -0
  3. package/dist/config/providers.d.ts +111 -0
  4. package/dist/config/providers.d.ts.map +1 -0
  5. package/dist/config/providers.js +1535 -0
  6. package/dist/config/providers.js.map +1 -0
  7. package/dist/index.d.ts +27 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +27 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/lib/api-helper.d.ts +35 -0
  12. package/dist/lib/api-helper.d.ts.map +1 -0
  13. package/dist/lib/api-helper.js +123 -0
  14. package/dist/lib/api-helper.js.map +1 -0
  15. package/dist/lib/capability-validator.d.ts +36 -0
  16. package/dist/lib/capability-validator.d.ts.map +1 -0
  17. package/dist/lib/capability-validator.js +140 -0
  18. package/dist/lib/capability-validator.js.map +1 -0
  19. package/dist/lib/media-constants.d.ts +55 -0
  20. package/dist/lib/media-constants.d.ts.map +1 -0
  21. package/dist/lib/media-constants.js +166 -0
  22. package/dist/lib/media-constants.js.map +1 -0
  23. package/dist/lib/media-conversion.d.ts +36 -0
  24. package/dist/lib/media-conversion.d.ts.map +1 -0
  25. package/dist/lib/media-conversion.js +87 -0
  26. package/dist/lib/media-conversion.js.map +1 -0
  27. package/dist/lib/media-utils.d.ts +35 -0
  28. package/dist/lib/media-utils.d.ts.map +1 -0
  29. package/dist/lib/media-utils.js +173 -0
  30. package/dist/lib/media-utils.js.map +1 -0
  31. package/dist/lib/output-limit.d.ts +38 -0
  32. package/dist/lib/output-limit.d.ts.map +1 -0
  33. package/dist/lib/output-limit.js +54 -0
  34. package/dist/lib/output-limit.js.map +1 -0
  35. package/dist/lib/prompt-builder.d.ts +94 -0
  36. package/dist/lib/prompt-builder.d.ts.map +1 -0
  37. package/dist/lib/prompt-builder.js +408 -0
  38. package/dist/lib/prompt-builder.js.map +1 -0
  39. package/dist/lib/reserved-fields.d.ts +41 -0
  40. package/dist/lib/reserved-fields.d.ts.map +1 -0
  41. package/dist/lib/reserved-fields.js +47 -0
  42. package/dist/lib/reserved-fields.js.map +1 -0
  43. package/dist/services/llm/anthropic.d.ts +16 -0
  44. package/dist/services/llm/anthropic.d.ts.map +1 -0
  45. package/dist/services/llm/anthropic.js +154 -0
  46. package/dist/services/llm/anthropic.js.map +1 -0
  47. package/dist/services/llm/custom.d.ts +51 -0
  48. package/dist/services/llm/custom.d.ts.map +1 -0
  49. package/dist/services/llm/custom.js +419 -0
  50. package/dist/services/llm/custom.js.map +1 -0
  51. package/dist/services/llm/extract-json.d.ts +17 -0
  52. package/dist/services/llm/extract-json.d.ts.map +1 -0
  53. package/dist/services/llm/extract-json.js +60 -0
  54. package/dist/services/llm/extract-json.js.map +1 -0
  55. package/dist/services/llm/finish-reason.d.ts +21 -0
  56. package/dist/services/llm/finish-reason.d.ts.map +1 -0
  57. package/dist/services/llm/finish-reason.js +55 -0
  58. package/dist/services/llm/finish-reason.js.map +1 -0
  59. package/dist/services/llm/gemini.d.ts +26 -0
  60. package/dist/services/llm/gemini.d.ts.map +1 -0
  61. package/dist/services/llm/gemini.js +178 -0
  62. package/dist/services/llm/gemini.js.map +1 -0
  63. package/dist/services/llm/groq.d.ts +28 -0
  64. package/dist/services/llm/groq.d.ts.map +1 -0
  65. package/dist/services/llm/groq.js +237 -0
  66. package/dist/services/llm/groq.js.map +1 -0
  67. package/dist/services/llm/index.d.ts +19 -0
  68. package/dist/services/llm/index.d.ts.map +1 -0
  69. package/dist/services/llm/index.js +88 -0
  70. package/dist/services/llm/index.js.map +1 -0
  71. package/dist/services/llm/openai.d.ts +101 -0
  72. package/dist/services/llm/openai.d.ts.map +1 -0
  73. package/dist/services/llm/openai.js +561 -0
  74. package/dist/services/llm/openai.js.map +1 -0
  75. package/dist/services/llm/types.d.ts +110 -0
  76. package/dist/services/llm/types.d.ts.map +1 -0
  77. package/dist/services/llm/types.js +16 -0
  78. package/dist/services/llm/types.js.map +1 -0
  79. package/dist/types/index.d.ts +812 -0
  80. package/dist/types/index.d.ts.map +1 -0
  81. package/dist/types/index.js +366 -0
  82. package/dist/types/index.js.map +1 -0
  83. package/package.json +93 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Pull the JSON out of a model reply that is not obliged to return only JSON.
3
+ *
4
+ * Function calling guarantees a clean payload; prompt-instructed output does
5
+ * not. A model asked in words for JSON may wrap it in a markdown fence, preface
6
+ * it with a sentence, or — on a reasoning model — put a whole `<think>` block in
7
+ * front of it. This is the tolerant reader for that case.
8
+ *
9
+ * It lived as a private method on the `lm_studio` provider, where it was written
10
+ * for local servers that support neither `tools` nor `response_format`. It moved
11
+ * here when DeepSeek needed exactly the same treatment for the opposite reason:
12
+ * not a server too simple for function calling, but a model too *new* for it —
13
+ * DeepSeek V4 is a thinking model and its API rejects `tool_choice` outright.
14
+ * One reader, so a fix to either provider's parsing is a fix to both.
15
+ */
16
+ export declare function extractJson(text: string): string;
17
+ //# sourceMappingURL=extract-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-json.d.ts","sourceRoot":"","sources":["../../../src/services/llm/extract-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA6ChD"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Pull the JSON out of a model reply that is not obliged to return only JSON.
3
+ *
4
+ * Function calling guarantees a clean payload; prompt-instructed output does
5
+ * not. A model asked in words for JSON may wrap it in a markdown fence, preface
6
+ * it with a sentence, or — on a reasoning model — put a whole `<think>` block in
7
+ * front of it. This is the tolerant reader for that case.
8
+ *
9
+ * It lived as a private method on the `lm_studio` provider, where it was written
10
+ * for local servers that support neither `tools` nor `response_format`. It moved
11
+ * here when DeepSeek needed exactly the same treatment for the opposite reason:
12
+ * not a server too simple for function calling, but a model too *new* for it —
13
+ * DeepSeek V4 is a thinking model and its API rejects `tool_choice` outright.
14
+ * One reader, so a fix to either provider's parsing is a fix to both.
15
+ */
16
+ export function extractJson(text) {
17
+ // Thinking blocks first: a reasoning model emits them before the answer, and
18
+ // they routinely contain braces that would otherwise be mistaken for it.
19
+ let cleaned = text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
20
+ // A fenced block is the strongest signal, so try it before anything looser —
21
+ // but only accept it if it actually parses, since a model will occasionally
22
+ // fence its reasoning and leave the real answer outside.
23
+ const codeBlockMatch = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/);
24
+ if (codeBlockMatch) {
25
+ try {
26
+ JSON.parse(codeBlockMatch[1].trim());
27
+ return codeBlockMatch[1].trim();
28
+ }
29
+ catch {
30
+ // Not the payload. Fall through to the looser strategies.
31
+ }
32
+ }
33
+ cleaned = cleaned
34
+ .replace(/```(?:json)?\s*/g, "")
35
+ .replace(/```/g, "")
36
+ .trim();
37
+ try {
38
+ JSON.parse(cleaned);
39
+ return cleaned;
40
+ }
41
+ catch {
42
+ // Still wrapped in prose.
43
+ }
44
+ // The first balanced-looking object or array. Greedy on purpose: the payload
45
+ // is usually the largest structure present, and a lazy match would stop at
46
+ // the first nested closing brace.
47
+ const jsonMatch = cleaned.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
48
+ if (jsonMatch) {
49
+ try {
50
+ JSON.parse(jsonMatch[1]);
51
+ return jsonMatch[1];
52
+ }
53
+ catch {
54
+ // Nothing parseable. The caller reports the raw reply, which is more
55
+ // useful than a guess.
56
+ }
57
+ }
58
+ return cleaned;
59
+ }
60
+ //# sourceMappingURL=extract-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-json.js","sourceRoot":"","sources":["../../../src/services/llm/extract-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,6EAA6E;IAC7E,yEAAyE;IACzE,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAEnE,6EAA6E;IAC7E,4EAA4E;IAC5E,yDAAyD;IACzD,MAAM,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACrE,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YACtC,OAAO,cAAc,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,0DAA0D;QAC5D,CAAC;IACH,CAAC;IAED,OAAO,GAAG,OAAO;SACd,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;SACnB,IAAI,EAAE,CAAC;IAEV,IAAI,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACpB,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,0BAA0B;IAC5B,CAAC;IAED,6EAA6E;IAC7E,2EAA2E;IAC3E,kCAAkC;IAClC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC7D,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC;YAC1B,OAAO,SAAS,CAAC,CAAC,CAAE,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;YACrE,uBAAuB;QACzB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @fileoverview Finish-reason normalization across LLM providers
3
+ * @description Providers report why generation stopped under different names
4
+ * (`finish_reason`, `stop_reason`, `finishReason`) and with different
5
+ * vocabularies. This maps all of them onto the {@link FinishReason} union that
6
+ * ShapeShyft reports to callers.
7
+ *
8
+ * The distinction that matters is `length`: a truncated answer usually fails
9
+ * schema validation, and without this a caller diagnoses a truncation as a
10
+ * malformed model -- a different fault with a different correct fix.
11
+ */
12
+ import type { FinishReason } from "../../types/index.js";
13
+ /**
14
+ * Normalize a provider's stop reason.
15
+ *
16
+ * @param raw - The provider's raw value, in whatever casing it used
17
+ * @returns The normalized reason, `"other"` for an unrecognized string, or
18
+ * `undefined` when the provider reported nothing usable
19
+ */
20
+ export declare function normalizeFinishReason(raw: unknown): FinishReason | undefined;
21
+ //# sourceMappingURL=finish-reason.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finish-reason.d.ts","sourceRoot":"","sources":["../../../src/services/llm/finish-reason.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAqCzD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAK5E"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview Finish-reason normalization across LLM providers
3
+ * @description Providers report why generation stopped under different names
4
+ * (`finish_reason`, `stop_reason`, `finishReason`) and with different
5
+ * vocabularies. This maps all of them onto the {@link FinishReason} union that
6
+ * ShapeShyft reports to callers.
7
+ *
8
+ * The distinction that matters is `length`: a truncated answer usually fails
9
+ * schema validation, and without this a caller diagnoses a truncation as a
10
+ * malformed model -- a different fault with a different correct fix.
11
+ */
12
+ /**
13
+ * Raw provider values, lowercased, mapped to normalized reasons.
14
+ *
15
+ * - OpenAI chat completions: `stop`, `length`, `tool_calls`, `content_filter`, `function_call`
16
+ * - OpenAI Responses API: `max_output_tokens`, `content_filter`
17
+ * - Anthropic: `end_turn`, `max_tokens`, `stop_sequence`, `tool_use`, `refusal`
18
+ * - Gemini: `STOP`, `MAX_TOKENS`, `SAFETY`, `RECITATION`, `OTHER`
19
+ */
20
+ const FINISH_REASON_MAP = {
21
+ // Natural completion
22
+ stop: "stop",
23
+ end_turn: "stop",
24
+ stop_sequence: "stop",
25
+ complete: "stop",
26
+ // Hit the token ceiling -- the reason this normalization exists
27
+ length: "length",
28
+ max_tokens: "length",
29
+ max_output_tokens: "length",
30
+ // Stopped by a safety system
31
+ content_filter: "content_filter",
32
+ safety: "content_filter",
33
+ recitation: "content_filter",
34
+ refusal: "content_filter",
35
+ blocklist: "content_filter",
36
+ // Stopped to call a tool
37
+ tool_calls: "tool_calls",
38
+ tool_use: "tool_calls",
39
+ function_call: "tool_calls",
40
+ other: "other",
41
+ };
42
+ /**
43
+ * Normalize a provider's stop reason.
44
+ *
45
+ * @param raw - The provider's raw value, in whatever casing it used
46
+ * @returns The normalized reason, `"other"` for an unrecognized string, or
47
+ * `undefined` when the provider reported nothing usable
48
+ */
49
+ export function normalizeFinishReason(raw) {
50
+ if (typeof raw !== "string" || raw.length === 0) {
51
+ return undefined;
52
+ }
53
+ return FINISH_REASON_MAP[raw.toLowerCase()] ?? "other";
54
+ }
55
+ //# sourceMappingURL=finish-reason.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finish-reason.js","sourceRoot":"","sources":["../../../src/services/llm/finish-reason.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAiC;IACtD,qBAAqB;IACrB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,MAAM;IAChB,aAAa,EAAE,MAAM;IACrB,QAAQ,EAAE,MAAM;IAEhB,gEAAgE;IAChE,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,QAAQ;IACpB,iBAAiB,EAAE,QAAQ;IAE3B,6BAA6B;IAC7B,cAAc,EAAE,gBAAgB;IAChC,MAAM,EAAE,gBAAgB;IACxB,UAAU,EAAE,gBAAgB;IAC5B,OAAO,EAAE,gBAAgB;IACzB,SAAS,EAAE,gBAAgB;IAE3B,yBAAyB;IACzB,UAAU,EAAE,YAAY;IACxB,QAAQ,EAAE,YAAY;IACtB,aAAa,EAAE,YAAY;IAE3B,KAAK,EAAE,OAAO;CACf,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAY;IAChD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,iBAAiB,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,OAAO,CAAC;AACzD,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @fileoverview Google Gemini LLM provider
3
+ * @description Implements the ILLMProvider interface for Gemini models.
4
+ * Uses native responseSchema for structured JSON output. Supports multimodal
5
+ * input (images, audio, video) via base64 and GCS URLs. Includes stubs
6
+ * for Imagen image generation and Veo video generation (requires Vertex AI SDK).
7
+ */
8
+ import type { ILLMProvider, LLMRequest, LLMResponse, ProviderConfig } from "./types.js";
9
+ export declare class GeminiProvider implements ILLMProvider {
10
+ readonly providerName: "gemini";
11
+ private genAI;
12
+ private defaultModel;
13
+ constructor(config: ProviderConfig);
14
+ generate(request: LLMRequest): Promise<LLMResponse>;
15
+ /**
16
+ * Generate media (images or video) using Imagen or Veo models.
17
+ * Note: This is a simplified implementation - production would use Vertex AI SDK.
18
+ */
19
+ private generateMedia;
20
+ /**
21
+ * Convert standard JSON Schema to Gemini's schema format
22
+ */
23
+ private convertToGeminiSchema;
24
+ buildApiPayload(request: LLMRequest): Record<string, unknown>;
25
+ }
26
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../../src/services/llm/gemini.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,WAAW,EACX,cAAc,EACf,MAAM,YAAY,CAAC;AAMpB,qBAAa,cAAe,YAAW,YAAY;IACjD,QAAQ,CAAC,YAAY,EAAG,QAAQ,CAAU;IAC1C,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,YAAY,CAAS;gBAEjB,MAAM,EAAE,cAAc;IAQ5B,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IA2EzD;;;OAGG;YACW,aAAa;IAwD3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAe7B,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAoC9D"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * @fileoverview Google Gemini LLM provider
3
+ * @description Implements the ILLMProvider interface for Gemini models.
4
+ * Uses native responseSchema for structured JSON output. Supports multimodal
5
+ * input (images, audio, video) via base64 and GCS URLs. Includes stubs
6
+ * for Imagen image generation and Veo video generation (requires Vertex AI SDK).
7
+ */
8
+ import { GoogleGenerativeAI, } from "@google/generative-ai";
9
+ import { isGenerativeModel } from "../../lib/capability-validator.js";
10
+ import { normalizeFinishReason } from "./finish-reason.js";
11
+ const DEFAULT_MODEL = "gemini-2.5-flash";
12
+ export class GeminiProvider {
13
+ providerName = "gemini";
14
+ genAI;
15
+ defaultModel;
16
+ constructor(config) {
17
+ if (!config.apiKey) {
18
+ throw new Error("Gemini API key is required");
19
+ }
20
+ this.genAI = new GoogleGenerativeAI(config.apiKey);
21
+ this.defaultModel = config.model ?? DEFAULT_MODEL;
22
+ }
23
+ async generate(request) {
24
+ const modelName = request.model ?? this.defaultModel;
25
+ const startTime = Date.now();
26
+ // Check if this is a generative model (Imagen, Veo)
27
+ if (isGenerativeModel(modelName)) {
28
+ return this.generateMedia(request, modelName, startTime);
29
+ }
30
+ // Build multimodal parts
31
+ const parts = [];
32
+ if (request.media?.length) {
33
+ for (const m of request.media) {
34
+ if (m.format === "base64") {
35
+ parts.push({
36
+ inlineData: { mimeType: m.mimeType, data: m.data },
37
+ });
38
+ }
39
+ else if (m.format === "url") {
40
+ // Only gs:// URLs are allowed (validated earlier)
41
+ parts.push({
42
+ fileData: { mimeType: m.mimeType, fileUri: m.data },
43
+ });
44
+ }
45
+ }
46
+ }
47
+ // Add text prompt
48
+ parts.push({ text: request.prompt });
49
+ // Create model with system instruction
50
+ const model = this.genAI.getGenerativeModel({
51
+ model: modelName,
52
+ systemInstruction: request.systemPrompt,
53
+ });
54
+ // Configure for JSON output with schema
55
+ const generationConfig = {
56
+ responseMimeType: "application/json",
57
+ responseSchema: this.convertToGeminiSchema(request.outputSchema),
58
+ temperature: request.temperature ?? 0,
59
+ maxOutputTokens: request.maxTokens,
60
+ };
61
+ const result = await model.generateContent({
62
+ contents: [{ role: "user", parts }],
63
+ generationConfig,
64
+ });
65
+ const latencyMs = Date.now() - startTime;
66
+ const response = result.response;
67
+ const rawResponse = response.text();
68
+ const content = JSON.parse(rawResponse);
69
+ // Gemini usage metadata
70
+ const usageMetadata = response.usageMetadata;
71
+ return {
72
+ content,
73
+ rawResponse,
74
+ usage: {
75
+ promptTokens: usageMetadata?.promptTokenCount ?? 0,
76
+ completionTokens: usageMetadata?.candidatesTokenCount ?? 0,
77
+ totalTokens: usageMetadata?.totalTokenCount ?? 0,
78
+ },
79
+ model: modelName,
80
+ provider: this.providerName,
81
+ latencyMs,
82
+ finishReason: normalizeFinishReason(response.candidates?.[0]?.finishReason),
83
+ };
84
+ }
85
+ /**
86
+ * Generate media (images or video) using Imagen or Veo models.
87
+ * Note: This is a simplified implementation - production would use Vertex AI SDK.
88
+ */
89
+ async generateMedia(_request, modelName, startTime) {
90
+ // Note: Imagen and Veo require Vertex AI SDK, not the basic genai SDK.
91
+ // This is a placeholder that shows the structure.
92
+ // In production, you would:
93
+ // 1. Use @google-cloud/vertexai package
94
+ // 2. Call imagen.generateImages() or veo.generateVideo()
95
+ // 3. Handle the response appropriately
96
+ if (modelName.includes("imagen")) {
97
+ // Placeholder for Imagen implementation
98
+ console.warn("Imagen generation requires Vertex AI SDK - not implemented in v1");
99
+ // For now, return a structured response indicating the limitation
100
+ return {
101
+ content: {
102
+ error: "Image generation requires Vertex AI SDK",
103
+ model: modelName,
104
+ },
105
+ rawResponse: JSON.stringify({ error: "Not implemented" }),
106
+ usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
107
+ model: modelName,
108
+ provider: this.providerName,
109
+ latencyMs: Date.now() - startTime,
110
+ generatedMedia: undefined,
111
+ };
112
+ }
113
+ if (modelName.includes("veo")) {
114
+ // Placeholder for Veo implementation
115
+ console.warn("Veo generation requires Vertex AI SDK - not implemented in v1");
116
+ return {
117
+ content: {
118
+ error: "Video generation requires Vertex AI SDK",
119
+ model: modelName,
120
+ },
121
+ rawResponse: JSON.stringify({ error: "Not implemented" }),
122
+ usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
123
+ model: modelName,
124
+ provider: this.providerName,
125
+ latencyMs: Date.now() - startTime,
126
+ generatedMedia: undefined,
127
+ };
128
+ }
129
+ throw new Error(`Unknown generative model: ${modelName}`);
130
+ }
131
+ /**
132
+ * Convert standard JSON Schema to Gemini's schema format
133
+ */
134
+ convertToGeminiSchema(jsonSchema) {
135
+ // Gemini mostly accepts standard JSON Schema, but may need adjustments
136
+ const geminiSchema = { ...jsonSchema };
137
+ // Remove unsupported keywords
138
+ const unsupportedKeywords = ["$schema", "$id", "definitions", "$defs"];
139
+ for (const keyword of unsupportedKeywords) {
140
+ delete geminiSchema[keyword];
141
+ }
142
+ return geminiSchema;
143
+ }
144
+ buildApiPayload(request) {
145
+ const modelName = request.model ?? this.defaultModel;
146
+ // Build multimodal parts
147
+ const parts = [];
148
+ if (request.media?.length) {
149
+ for (const m of request.media) {
150
+ if (m.format === "base64") {
151
+ parts.push({
152
+ inlineData: { mimeType: m.mimeType, data: m.data },
153
+ });
154
+ }
155
+ else if (m.format === "url") {
156
+ parts.push({
157
+ fileData: { mimeType: m.mimeType, fileUri: m.data },
158
+ });
159
+ }
160
+ }
161
+ }
162
+ parts.push({ text: request.prompt });
163
+ return {
164
+ model: modelName,
165
+ contents: [{ parts }],
166
+ systemInstruction: request.systemPrompt
167
+ ? { parts: [{ text: request.systemPrompt }] }
168
+ : undefined,
169
+ generationConfig: {
170
+ responseMimeType: "application/json",
171
+ responseSchema: this.convertToGeminiSchema(request.outputSchema),
172
+ temperature: request.temperature ?? 0,
173
+ maxOutputTokens: request.maxTokens,
174
+ },
175
+ };
176
+ }
177
+ }
178
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../src/services/llm/gemini.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Groq LLM Provider
3
+ *
4
+ * Groq provides fast inference for various models including Whisper for transcription.
5
+ * For chat completions, Groq uses an OpenAI-compatible API format.
6
+ * For Whisper transcription, a dedicated implementation is needed.
7
+ */
8
+ import type { ILLMProvider, LLMRequest, LLMResponse, ProviderConfig } from "./types.js";
9
+ export declare class GroqProvider implements ILLMProvider {
10
+ readonly providerName: "groq";
11
+ private client;
12
+ private defaultModel;
13
+ private apiKey;
14
+ constructor(config: ProviderConfig);
15
+ generate(request: LLMRequest): Promise<LLMResponse>;
16
+ /**
17
+ * Generate transcription using Whisper model.
18
+ * If an extraction model is configured, the transcription is fed through
19
+ * that model to produce structured output.
20
+ */
21
+ private generateTranscription;
22
+ /**
23
+ * Generate chat completion using Groq's OpenAI-compatible API.
24
+ */
25
+ private generateChatCompletion;
26
+ buildApiPayload(request: LLMRequest): Record<string, unknown>;
27
+ }
28
+ //# sourceMappingURL=groq.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groq.d.ts","sourceRoot":"","sources":["../../../src/services/llm/groq.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,WAAW,EACX,cAAc,EACf,MAAM,YAAY,CAAC;AA4CpB,qBAAa,YAAa,YAAW,YAAY;IAC/C,QAAQ,CAAC,YAAY,EAAG,MAAM,CAAU;IACxC,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,cAAc;IAS5B,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAYzD;;;;OAIG;YACW,qBAAqB;IAmGnC;;OAEG;YACW,sBAAsB;IAmEpC,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAyC9D"}
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Groq LLM Provider
3
+ *
4
+ * Groq provides fast inference for various models including Whisper for transcription.
5
+ * For chat completions, Groq uses an OpenAI-compatible API format.
6
+ * For Whisper transcription, a dedicated implementation is needed.
7
+ */
8
+ import Groq from "groq-sdk";
9
+ import { toFile } from "groq-sdk/uploads";
10
+ import { createLLMProvider } from "./index.js";
11
+ import { getProviderForModel } from "../../config/providers.js";
12
+ import { isTranscriptionModel } from "../../lib/capability-validator.js";
13
+ import { normalizeFinishReason } from "./finish-reason.js";
14
+ const DEFAULT_MODEL = "llama-3.3-70b-versatile";
15
+ const DEFAULT_WHISPER_MODEL = "whisper-large-v3";
16
+ /**
17
+ * Convert base64 audio data to an Uploadable for the Groq SDK.
18
+ */
19
+ async function base64ToUploadable(base64Data, mimeType, fieldName) {
20
+ // Validate base64 before decoding
21
+ if (!/^[A-Za-z0-9+/=]+$/.test(base64Data)) {
22
+ throw new Error("Invalid base64 encoding in audio data");
23
+ }
24
+ // Decode base64 to binary
25
+ const binaryString = atob(base64Data);
26
+ const bytes = new Uint8Array(binaryString.length);
27
+ for (let i = 0; i < binaryString.length; i++) {
28
+ bytes[i] = binaryString.charCodeAt(i);
29
+ }
30
+ // Validate buffer is not empty
31
+ if (bytes.length === 0) {
32
+ throw new Error("Audio data decoded to empty buffer");
33
+ }
34
+ // Determine file extension from MIME type
35
+ const extension = mimeType.split("/")[1] ?? "mp3";
36
+ const filename = fieldName
37
+ ? `${fieldName}.${extension}`
38
+ : `audio.${extension}`;
39
+ // Use Groq SDK's toFile utility to create a proper Uploadable
40
+ return toFile(bytes, filename, { type: mimeType });
41
+ }
42
+ export class GroqProvider {
43
+ providerName = "groq";
44
+ client;
45
+ defaultModel;
46
+ apiKey;
47
+ constructor(config) {
48
+ if (!config.apiKey) {
49
+ throw new Error("Groq API key is required");
50
+ }
51
+ this.apiKey = config.apiKey;
52
+ this.client = new Groq({ apiKey: config.apiKey });
53
+ this.defaultModel = config.model ?? DEFAULT_MODEL;
54
+ }
55
+ async generate(request) {
56
+ const model = request.model ?? this.defaultModel;
57
+ // Check if this is a transcription model (Whisper)
58
+ if (isTranscriptionModel(model)) {
59
+ return this.generateTranscription(request, model);
60
+ }
61
+ // For non-Whisper models, use chat completions (OpenAI-compatible)
62
+ return this.generateChatCompletion(request, model);
63
+ }
64
+ /**
65
+ * Generate transcription using Whisper model.
66
+ * If an extraction model is configured, the transcription is fed through
67
+ * that model to produce structured output.
68
+ */
69
+ async generateTranscription(request, model) {
70
+ const startTime = Date.now();
71
+ // Validate: exactly one audio input
72
+ const audioMedia = request.media?.filter(m => m.type === "audio");
73
+ if (!audioMedia || audioMedia.length === 0) {
74
+ throw new Error("Whisper requires exactly one audio input");
75
+ }
76
+ if (audioMedia.length > 1) {
77
+ throw new Error(`Whisper accepts only one audio input, got ${audioMedia.length}`);
78
+ }
79
+ const audio = audioMedia[0];
80
+ // Convert base64 to Uploadable for SDK
81
+ let audioFile;
82
+ try {
83
+ audioFile = await base64ToUploadable(audio.data, audio.mimeType, audio.fieldName);
84
+ }
85
+ catch (error) {
86
+ throw new Error(`Invalid audio data: ${error instanceof Error ? error.message : error}`);
87
+ }
88
+ // Transcribe using Groq Whisper
89
+ const transcription = await this.client.audio.transcriptions.create({
90
+ file: audioFile,
91
+ model: model || DEFAULT_WHISPER_MODEL,
92
+ });
93
+ const transcriptionText = transcription.text;
94
+ const transcriptionLatency = Date.now() - startTime;
95
+ // If no extraction model configured, return raw transcription
96
+ if (!request.extractionModel) {
97
+ return {
98
+ content: { transcription: transcriptionText },
99
+ rawResponse: transcriptionText,
100
+ usage: {
101
+ promptTokens: 0,
102
+ completionTokens: 0,
103
+ totalTokens: 0,
104
+ },
105
+ model: model || DEFAULT_WHISPER_MODEL,
106
+ provider: this.providerName,
107
+ latencyMs: transcriptionLatency,
108
+ // Whisper transcribes to completion; there is no token ceiling to hit.
109
+ finishReason: "stop",
110
+ };
111
+ }
112
+ // Feed transcription through extraction model for structured output
113
+ const extractionProvider = createLLMProvider(getProviderForModel(request.extractionModel), {
114
+ apiKey: request.extractionApiKey,
115
+ model: request.extractionModel,
116
+ });
117
+ // Build extraction prompt that includes the transcription
118
+ const extractionPrompt = request.prompt
119
+ ? `${request.prompt}\n\nTranscription:\n${transcriptionText}`
120
+ : `Extract structured data from this transcription:\n\n${transcriptionText}`;
121
+ const extractionRequest = {
122
+ prompt: extractionPrompt,
123
+ systemPrompt: request.systemPrompt,
124
+ outputSchema: request.outputSchema,
125
+ model: request.extractionModel,
126
+ temperature: request.temperature,
127
+ maxTokens: request.maxTokens,
128
+ };
129
+ const extractionResponse = await extractionProvider.generate(extractionRequest);
130
+ // Combine latency from both steps
131
+ return {
132
+ ...extractionResponse,
133
+ // Override latency to include both transcription and extraction
134
+ latencyMs: Date.now() - startTime,
135
+ // Include raw transcription in response for debugging
136
+ rawResponse: JSON.stringify({
137
+ transcription: transcriptionText,
138
+ extraction: extractionResponse.rawResponse,
139
+ }),
140
+ };
141
+ }
142
+ /**
143
+ * Generate chat completion using Groq's OpenAI-compatible API.
144
+ */
145
+ async generateChatCompletion(request, model) {
146
+ const startTime = Date.now();
147
+ // Build messages
148
+ const messages = [];
149
+ if (request.systemPrompt) {
150
+ messages.push({ role: "system", content: request.systemPrompt });
151
+ }
152
+ // For Groq chat models, we don't have native multimodal support
153
+ // Media should have been extracted and replaced with placeholders
154
+ messages.push({ role: "user", content: request.prompt });
155
+ // Use function calling for structured output
156
+ const tools = [
157
+ {
158
+ type: "function",
159
+ function: {
160
+ name: "structured_response",
161
+ description: "Generate structured response matching the schema",
162
+ parameters: request.outputSchema,
163
+ },
164
+ },
165
+ ];
166
+ const response = await this.client.chat.completions.create({
167
+ model,
168
+ messages,
169
+ tools,
170
+ tool_choice: {
171
+ type: "function",
172
+ function: { name: "structured_response" },
173
+ },
174
+ temperature: request.temperature ?? 0,
175
+ max_tokens: request.maxTokens,
176
+ });
177
+ const latencyMs = Date.now() - startTime;
178
+ // Extract structured response from function call
179
+ const toolCall = response.choices[0]?.message.tool_calls?.[0];
180
+ if (!toolCall || toolCall.function.name !== "structured_response") {
181
+ throw new Error("Expected function call response from Groq");
182
+ }
183
+ const rawResponse = toolCall.function.arguments;
184
+ const content = JSON.parse(rawResponse);
185
+ return {
186
+ content,
187
+ rawResponse,
188
+ usage: {
189
+ promptTokens: response.usage?.prompt_tokens ?? 0,
190
+ completionTokens: response.usage?.completion_tokens ?? 0,
191
+ totalTokens: response.usage?.total_tokens ?? 0,
192
+ },
193
+ model: response.model,
194
+ provider: this.providerName,
195
+ latencyMs,
196
+ finishReason: normalizeFinishReason(response.choices[0]?.finish_reason),
197
+ };
198
+ }
199
+ buildApiPayload(request) {
200
+ const model = request.model ?? this.defaultModel;
201
+ // For Whisper, return transcription request format
202
+ if (isTranscriptionModel(model)) {
203
+ return {
204
+ model,
205
+ // Note: file would be added separately as multipart form data
206
+ response_format: "json",
207
+ };
208
+ }
209
+ // For chat models, return chat completion format
210
+ const messages = [];
211
+ if (request.systemPrompt) {
212
+ messages.push({ role: "system", content: request.systemPrompt });
213
+ }
214
+ messages.push({ role: "user", content: request.prompt });
215
+ return {
216
+ model,
217
+ messages,
218
+ tools: [
219
+ {
220
+ type: "function",
221
+ function: {
222
+ name: "structured_response",
223
+ description: "Generate structured response matching the schema",
224
+ parameters: request.outputSchema,
225
+ },
226
+ },
227
+ ],
228
+ tool_choice: {
229
+ type: "function",
230
+ function: { name: "structured_response" },
231
+ },
232
+ temperature: request.temperature ?? 0,
233
+ max_tokens: request.maxTokens,
234
+ };
235
+ }
236
+ }
237
+ //# sourceMappingURL=groq.js.map