@marinoscar/platform-api 0.1.0-next.4 → 0.1.0-next.6
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/CHANGELOG.md +18 -0
- package/dist/ai/core/ai-error.d.ts +15 -158
- package/dist/ai/core/ai-error.d.ts.map +1 -1
- package/dist/ai/core/ai-error.js +58 -221
- package/dist/ai/core/ai-error.js.map +1 -1
- package/dist/ai/core/capabilities.d.ts +3 -126
- package/dist/ai/core/capabilities.d.ts.map +1 -1
- package/dist/ai/core/capabilities.js +13 -109
- package/dist/ai/core/capabilities.js.map +1 -1
- package/dist/ai/core/conversation.d.ts +2 -15
- package/dist/ai/core/conversation.d.ts.map +1 -1
- package/dist/ai/core/conversation.js +9 -54
- package/dist/ai/core/conversation.js.map +1 -1
- package/dist/ai/core/hosted-tools.d.ts +3 -172
- package/dist/ai/core/hosted-tools.d.ts.map +1 -1
- package/dist/ai/core/hosted-tools.js +20 -286
- package/dist/ai/core/hosted-tools.js.map +1 -1
- package/dist/ai/core/provider-adapter.interface.d.ts +3 -260
- package/dist/ai/core/provider-adapter.interface.d.ts.map +1 -1
- package/dist/ai/core/provider-adapter.interface.js +7 -29
- package/dist/ai/core/provider-adapter.interface.js.map +1 -1
- package/dist/ai/core/structured-output.d.ts +3 -49
- package/dist/ai/core/structured-output.d.ts.map +1 -1
- package/dist/ai/core/structured-output.js +9 -80
- package/dist/ai/core/structured-output.js.map +1 -1
- package/dist/ai/core/tools.d.ts +3 -93
- package/dist/ai/core/tools.d.ts.map +1 -1
- package/dist/ai/core/tools.js +9 -73
- package/dist/ai/core/tools.js.map +1 -1
- package/dist/ai/core/types/file-inputs.types.d.ts +3 -102
- package/dist/ai/core/types/file-inputs.types.d.ts.map +1 -1
- package/dist/ai/core/types/file-inputs.types.js +14 -78
- package/dist/ai/core/types/file-inputs.types.js.map +1 -1
- package/dist/ai/core/types/media.types.d.ts +3 -616
- package/dist/ai/core/types/media.types.d.ts.map +1 -1
- package/dist/ai/core/types/media.types.js +33 -203
- package/dist/ai/core/types/media.types.js.map +1 -1
- package/dist/ai/core/types/responses.types.d.ts +3 -619
- package/dist/ai/core/types/responses.types.d.ts.map +1 -1
- package/dist/ai/core/types/responses.types.js +9 -57
- package/dist/ai/core/types/responses.types.js.map +1 -1
- package/dist/ai/http/dto/ai-images.dto.d.ts +4 -4
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +3 -3
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/providers/anthropic/anthropic-client.factory.d.ts +13 -15
- package/dist/ai/providers/anthropic/anthropic-client.factory.d.ts.map +1 -1
- package/dist/ai/providers/anthropic/anthropic-client.factory.js +23 -52
- package/dist/ai/providers/anthropic/anthropic-client.factory.js.map +1 -1
- package/dist/ai/providers/anthropic/anthropic.adapter.d.ts +15 -51
- package/dist/ai/providers/anthropic/anthropic.adapter.d.ts.map +1 -1
- package/dist/ai/providers/anthropic/anthropic.adapter.js +29 -293
- package/dist/ai/providers/anthropic/anthropic.adapter.js.map +1 -1
- package/dist/ai/providers/azure-openai/azure-openai-client.factory.d.ts +10 -14
- package/dist/ai/providers/azure-openai/azure-openai-client.factory.d.ts.map +1 -1
- package/dist/ai/providers/azure-openai/azure-openai-client.factory.js +20 -53
- package/dist/ai/providers/azure-openai/azure-openai-client.factory.js.map +1 -1
- package/dist/ai/providers/azure-openai/azure-openai.adapter.d.ts +14 -31
- package/dist/ai/providers/azure-openai/azure-openai.adapter.d.ts.map +1 -1
- package/dist/ai/providers/azure-openai/azure-openai.adapter.js +25 -170
- package/dist/ai/providers/azure-openai/azure-openai.adapter.js.map +1 -1
- package/dist/ai/providers/gemini/gemini-client.factory.d.ts +13 -18
- package/dist/ai/providers/gemini/gemini-client.factory.d.ts.map +1 -1
- package/dist/ai/providers/gemini/gemini-client.factory.js +24 -68
- package/dist/ai/providers/gemini/gemini-client.factory.js.map +1 -1
- package/dist/ai/providers/gemini/gemini.adapter.d.ts +15 -58
- package/dist/ai/providers/gemini/gemini.adapter.d.ts.map +1 -1
- package/dist/ai/providers/gemini/gemini.adapter.js +29 -348
- package/dist/ai/providers/gemini/gemini.adapter.js.map +1 -1
- package/dist/ai/providers/openai/openai-client.factory.d.ts +13 -35
- package/dist/ai/providers/openai/openai-client.factory.d.ts.map +1 -1
- package/dist/ai/providers/openai/openai-client.factory.js +25 -64
- package/dist/ai/providers/openai/openai-client.factory.js.map +1 -1
- package/dist/ai/providers/openai/openai.adapter.d.ts +15 -48
- package/dist/ai/providers/openai/openai.adapter.d.ts.map +1 -1
- package/dist/ai/providers/openai/openai.adapter.js +27 -206
- package/dist/ai/providers/openai/openai.adapter.js.map +1 -1
- package/dist/ai/providers/openai-compatible/openai-compatible-client.factory.d.ts +11 -11
- package/dist/ai/providers/openai-compatible/openai-compatible-client.factory.d.ts.map +1 -1
- package/dist/ai/providers/openai-compatible/openai-compatible-client.factory.js +18 -41
- package/dist/ai/providers/openai-compatible/openai-compatible-client.factory.js.map +1 -1
- package/dist/ai/providers/openai-compatible/openai-compatible.adapter.d.ts +14 -32
- package/dist/ai/providers/openai-compatible/openai-compatible.adapter.d.ts.map +1 -1
- package/dist/ai/providers/openai-compatible/openai-compatible.adapter.js +25 -149
- package/dist/ai/providers/openai-compatible/openai-compatible.adapter.js.map +1 -1
- package/dist/ai/providers/platform-ai-tracer.d.ts +4 -0
- package/dist/ai/providers/platform-ai-tracer.d.ts.map +1 -0
- package/dist/ai/providers/platform-ai-tracer.js +20 -0
- package/dist/ai/providers/platform-ai-tracer.js.map +1 -0
- package/dist/ai/runtime/ai-image-run-request.d.ts +1 -1
- package/dist/ai/testing/conformance/ai-no-sdk-leak.suite.d.ts +28 -5
- package/dist/ai/testing/conformance/ai-no-sdk-leak.suite.d.ts.map +1 -1
- package/dist/ai/testing/conformance/ai-no-sdk-leak.suite.js +54 -9
- package/dist/ai/testing/conformance/ai-no-sdk-leak.suite.js.map +1 -1
- package/dist/ai/testing/index.d.ts +1 -1
- package/dist/ai/testing/index.d.ts.map +1 -1
- package/dist/ai/testing/index.js +2 -1
- package/dist/ai/testing/index.js.map +1 -1
- package/dist/jobs/rate-limit.error.d.ts +2 -26
- package/dist/jobs/rate-limit.error.d.ts.map +1 -1
- package/dist/jobs/rate-limit.error.js +4 -52
- package/dist/jobs/rate-limit.error.js.map +1 -1
- package/dist/storage/config/storage-config.controller.d.ts +1 -1
- package/package.json +3 -2
- package/dist/ai/providers/anthropic/anthropic-errors.d.ts +0 -16
- package/dist/ai/providers/anthropic/anthropic-errors.d.ts.map +0 -1
- package/dist/ai/providers/anthropic/anthropic-errors.js +0 -156
- package/dist/ai/providers/anthropic/anthropic-errors.js.map +0 -1
- package/dist/ai/providers/anthropic/anthropic-messages.mapper.d.ts +0 -80
- package/dist/ai/providers/anthropic/anthropic-messages.mapper.d.ts.map +0 -1
- package/dist/ai/providers/anthropic/anthropic-messages.mapper.js +0 -536
- package/dist/ai/providers/anthropic/anthropic-messages.mapper.js.map +0 -1
- package/dist/ai/providers/anthropic/anthropic-model-catalog.d.ts +0 -49
- package/dist/ai/providers/anthropic/anthropic-model-catalog.d.ts.map +0 -1
- package/dist/ai/providers/anthropic/anthropic-model-catalog.js +0 -198
- package/dist/ai/providers/anthropic/anthropic-model-catalog.js.map +0 -1
- package/dist/ai/providers/anthropic/anthropic-stream.mapper.d.ts +0 -35
- package/dist/ai/providers/anthropic/anthropic-stream.mapper.d.ts.map +0 -1
- package/dist/ai/providers/anthropic/anthropic-stream.mapper.js +0 -203
- package/dist/ai/providers/anthropic/anthropic-stream.mapper.js.map +0 -1
- package/dist/ai/providers/anthropic/testing/anthropic-fixtures.d.ts +0 -18
- package/dist/ai/providers/anthropic/testing/anthropic-fixtures.d.ts.map +0 -1
- package/dist/ai/providers/anthropic/testing/anthropic-fixtures.js +0 -120
- package/dist/ai/providers/anthropic/testing/anthropic-fixtures.js.map +0 -1
- package/dist/ai/providers/anthropic/testing/anthropic-mock-transport.d.ts +0 -59
- package/dist/ai/providers/anthropic/testing/anthropic-mock-transport.d.ts.map +0 -1
- package/dist/ai/providers/anthropic/testing/anthropic-mock-transport.js +0 -206
- package/dist/ai/providers/anthropic/testing/anthropic-mock-transport.js.map +0 -1
- package/dist/ai/providers/azure-openai/azure-openai-settings.d.ts +0 -23
- package/dist/ai/providers/azure-openai/azure-openai-settings.d.ts.map +0 -1
- package/dist/ai/providers/azure-openai/azure-openai-settings.js +0 -54
- package/dist/ai/providers/azure-openai/azure-openai-settings.js.map +0 -1
- package/dist/ai/providers/gemini/gemini-content.mapper.d.ts +0 -98
- package/dist/ai/providers/gemini/gemini-content.mapper.d.ts.map +0 -1
- package/dist/ai/providers/gemini/gemini-content.mapper.js +0 -665
- package/dist/ai/providers/gemini/gemini-content.mapper.js.map +0 -1
- package/dist/ai/providers/gemini/gemini-embeddings.mapper.d.ts +0 -13
- package/dist/ai/providers/gemini/gemini-embeddings.mapper.d.ts.map +0 -1
- package/dist/ai/providers/gemini/gemini-embeddings.mapper.js +0 -78
- package/dist/ai/providers/gemini/gemini-embeddings.mapper.js.map +0 -1
- package/dist/ai/providers/gemini/gemini-errors.d.ts +0 -32
- package/dist/ai/providers/gemini/gemini-errors.d.ts.map +0 -1
- package/dist/ai/providers/gemini/gemini-errors.js +0 -191
- package/dist/ai/providers/gemini/gemini-errors.js.map +0 -1
- package/dist/ai/providers/gemini/gemini-model-catalog.d.ts +0 -45
- package/dist/ai/providers/gemini/gemini-model-catalog.d.ts.map +0 -1
- package/dist/ai/providers/gemini/gemini-model-catalog.js +0 -264
- package/dist/ai/providers/gemini/gemini-model-catalog.js.map +0 -1
- package/dist/ai/providers/gemini/gemini-stream.mapper.d.ts +0 -31
- package/dist/ai/providers/gemini/gemini-stream.mapper.d.ts.map +0 -1
- package/dist/ai/providers/gemini/gemini-stream.mapper.js +0 -118
- package/dist/ai/providers/gemini/gemini-stream.mapper.js.map +0 -1
- package/dist/ai/providers/gemini/testing/gemini-fixtures.d.ts +0 -80
- package/dist/ai/providers/gemini/testing/gemini-fixtures.d.ts.map +0 -1
- package/dist/ai/providers/gemini/testing/gemini-fixtures.js +0 -118
- package/dist/ai/providers/gemini/testing/gemini-fixtures.js.map +0 -1
- package/dist/ai/providers/gemini/testing/gemini-mock-transport.d.ts +0 -82
- package/dist/ai/providers/gemini/testing/gemini-mock-transport.d.ts.map +0 -1
- package/dist/ai/providers/gemini/testing/gemini-mock-transport.js +0 -342
- package/dist/ai/providers/gemini/testing/gemini-mock-transport.js.map +0 -1
- package/dist/ai/providers/openai/openai-audio.mapper.d.ts +0 -42
- package/dist/ai/providers/openai/openai-audio.mapper.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-audio.mapper.js +0 -266
- package/dist/ai/providers/openai/openai-audio.mapper.js.map +0 -1
- package/dist/ai/providers/openai/openai-call-telemetry.d.ts +0 -25
- package/dist/ai/providers/openai/openai-call-telemetry.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-call-telemetry.js +0 -93
- package/dist/ai/providers/openai/openai-call-telemetry.js.map +0 -1
- package/dist/ai/providers/openai/openai-chat-completions-stream.mapper.d.ts +0 -41
- package/dist/ai/providers/openai/openai-chat-completions-stream.mapper.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-chat-completions-stream.mapper.js +0 -173
- package/dist/ai/providers/openai/openai-chat-completions-stream.mapper.js.map +0 -1
- package/dist/ai/providers/openai/openai-chat-completions.engine.d.ts +0 -33
- package/dist/ai/providers/openai/openai-chat-completions.engine.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-chat-completions.engine.js +0 -165
- package/dist/ai/providers/openai/openai-chat-completions.engine.js.map +0 -1
- package/dist/ai/providers/openai/openai-chat-completions.mapper.d.ts +0 -42
- package/dist/ai/providers/openai/openai-chat-completions.mapper.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-chat-completions.mapper.js +0 -304
- package/dist/ai/providers/openai/openai-chat-completions.mapper.js.map +0 -1
- package/dist/ai/providers/openai/openai-embeddings.mapper.d.ts +0 -25
- package/dist/ai/providers/openai/openai-embeddings.mapper.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-embeddings.mapper.js +0 -81
- package/dist/ai/providers/openai/openai-embeddings.mapper.js.map +0 -1
- package/dist/ai/providers/openai/openai-errors.d.ts +0 -45
- package/dist/ai/providers/openai/openai-errors.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-errors.js +0 -247
- package/dist/ai/providers/openai/openai-errors.js.map +0 -1
- package/dist/ai/providers/openai/openai-family-guards.d.ts +0 -4
- package/dist/ai/providers/openai/openai-family-guards.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-family-guards.js +0 -19
- package/dist/ai/providers/openai/openai-family-guards.js.map +0 -1
- package/dist/ai/providers/openai/openai-images.mapper.d.ts +0 -23
- package/dist/ai/providers/openai/openai-images.mapper.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-images.mapper.js +0 -164
- package/dist/ai/providers/openai/openai-images.mapper.js.map +0 -1
- package/dist/ai/providers/openai/openai-model-catalog.d.ts +0 -36
- package/dist/ai/providers/openai/openai-model-catalog.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-model-catalog.js +0 -209
- package/dist/ai/providers/openai/openai-model-catalog.js.map +0 -1
- package/dist/ai/providers/openai/openai-realtime.mapper.d.ts +0 -18
- package/dist/ai/providers/openai/openai-realtime.mapper.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-realtime.mapper.js +0 -159
- package/dist/ai/providers/openai/openai-realtime.mapper.js.map +0 -1
- package/dist/ai/providers/openai/openai-redirect-guard.d.ts +0 -9
- package/dist/ai/providers/openai/openai-redirect-guard.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-redirect-guard.js +0 -61
- package/dist/ai/providers/openai/openai-redirect-guard.js.map +0 -1
- package/dist/ai/providers/openai/openai-responses.engine.d.ts +0 -50
- package/dist/ai/providers/openai/openai-responses.engine.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-responses.engine.js +0 -206
- package/dist/ai/providers/openai/openai-responses.engine.js.map +0 -1
- package/dist/ai/providers/openai/openai-responses.mapper.d.ts +0 -64
- package/dist/ai/providers/openai/openai-responses.mapper.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-responses.mapper.js +0 -580
- package/dist/ai/providers/openai/openai-responses.mapper.js.map +0 -1
- package/dist/ai/providers/openai/openai-stream.mapper.d.ts +0 -28
- package/dist/ai/providers/openai/openai-stream.mapper.d.ts.map +0 -1
- package/dist/ai/providers/openai/openai-stream.mapper.js +0 -139
- package/dist/ai/providers/openai/openai-stream.mapper.js.map +0 -1
- package/dist/ai/providers/openai/testing/chat-completions-fixtures.d.ts +0 -24
- package/dist/ai/providers/openai/testing/chat-completions-fixtures.d.ts.map +0 -1
- package/dist/ai/providers/openai/testing/chat-completions-fixtures.js +0 -72
- package/dist/ai/providers/openai/testing/chat-completions-fixtures.js.map +0 -1
- package/dist/ai/providers/openai/testing/conformance-responders.d.ts +0 -7
- package/dist/ai/providers/openai/testing/conformance-responders.d.ts.map +0 -1
- package/dist/ai/providers/openai/testing/conformance-responders.js +0 -64
- package/dist/ai/providers/openai/testing/conformance-responders.js.map +0 -1
- package/dist/ai/providers/openai/testing/openai-fixtures.d.ts +0 -52
- package/dist/ai/providers/openai/testing/openai-fixtures.d.ts.map +0 -1
- package/dist/ai/providers/openai/testing/openai-fixtures.js +0 -208
- package/dist/ai/providers/openai/testing/openai-fixtures.js.map +0 -1
- package/dist/ai/providers/openai/testing/openai-mock-transport.d.ts +0 -242
- package/dist/ai/providers/openai/testing/openai-mock-transport.d.ts.map +0 -1
- package/dist/ai/providers/openai/testing/openai-mock-transport.js +0 -523
- package/dist/ai/providers/openai/testing/openai-mock-transport.js.map +0 -1
- package/dist/ai/providers/openai-compatible/openai-compatible-settings.d.ts +0 -13
- package/dist/ai/providers/openai-compatible/openai-compatible-settings.d.ts.map +0 -1
- package/dist/ai/providers/openai-compatible/openai-compatible-settings.js +0 -29
- package/dist/ai/providers/openai-compatible/openai-compatible-settings.js.map +0 -1
|
@@ -1,665 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// =============================================================================
|
|
3
|
-
// Our Responses types <-> Gemini `generateContent` (issue #447, epic #421)
|
|
4
|
-
// =============================================================================
|
|
5
|
-
//
|
|
6
|
-
// Pure functions, no I/O: `toGeminiRequest` builds the SDK parameters from an
|
|
7
|
-
// `AiResponseRequest`; `GeminiOutputAssembler` turns response PARTS into our
|
|
8
|
-
// output items, and is shared by `create` (`fromGeminiResponse`) and the
|
|
9
|
-
// stream mapper, so a streamed and a non-streamed call cannot disagree about
|
|
10
|
-
// what a response means.
|
|
11
|
-
//
|
|
12
|
-
// REQUEST MAPPING
|
|
13
|
-
//
|
|
14
|
-
// instructions (+ system/developer messages) -> `systemInstruction`
|
|
15
|
-
// message (user) -> user turn: `text`; `inlineData` (base64) for a
|
|
16
|
-
// `data:` URL or a storage object's bytes;
|
|
17
|
-
// `fileData { fileUri }` for any other URL
|
|
18
|
-
// message (assistant) -> model turn, text only
|
|
19
|
-
// function_call -> model `functionCall { name, args, id? }`
|
|
20
|
-
// function_call_output -> user `functionResponse { name, response, id? }`
|
|
21
|
-
// — Gemini keys a response by the function NAME,
|
|
22
|
-
// which is read off the matching `function_call`
|
|
23
|
-
// earlier in the (full-history) input
|
|
24
|
-
// reasoning -> the `thoughtSignature` it carries as provider
|
|
25
|
-
// state (`AI_PROVIDER_STATE`), put back on the
|
|
26
|
-
// part it came from (see REPLAY below); a
|
|
27
|
-
// reasoning item without Gemini state is dropped
|
|
28
|
-
// function tools -> one tool of `functionDeclarations`
|
|
29
|
-
// (`parametersJsonSchema`)
|
|
30
|
-
// toolChoice -> `functionCallingConfig.mode` AUTO | NONE | ANY,
|
|
31
|
-
// a named function -> ANY + `allowedFunctionNames`
|
|
32
|
-
// reasoning -> `thinkingConfig { includeThoughts: true, ... }`:
|
|
33
|
-
// 'level' families `thinkingLevel`, 'budget'
|
|
34
|
-
// families (and unclassified models)
|
|
35
|
-
// `thinkingBudget` (`GEMINI_THINKING_BUDGETS`)
|
|
36
|
-
// structuredOutput -> `responseMimeType: 'application/json'` +
|
|
37
|
-
// `responseJsonSchema`, validated with
|
|
38
|
-
// `parseStructured`
|
|
39
|
-
// maxOutputTokens / temperature -> the same-named config fields
|
|
40
|
-
//
|
|
41
|
-
// Consecutive items of the same role merge into one turn, so a replayed round
|
|
42
|
-
// (a model turn of text + function calls, then a user turn of all their
|
|
43
|
-
// responses) is exactly the alternating shape Gemini requires.
|
|
44
|
-
//
|
|
45
|
-
// THOUGHTS ARE NEVER EXPOSED RAW. With `includeThoughts`, Gemini returns
|
|
46
|
-
// thought SUMMARIES (parts marked `thought: true`); their text becomes a
|
|
47
|
-
// reasoning item's `summary`. A part's `thoughtSignature` — Gemini's opaque,
|
|
48
|
-
// encrypted reasoning state, required back on a function-call turn by Gemini
|
|
49
|
-
// 3 — travels only as symbol-keyed provider state, never in a serialisable
|
|
50
|
-
// field.
|
|
51
|
-
//
|
|
52
|
-
// REPLAY. A signature belongs to one PART, so its reasoning item names the
|
|
53
|
-
// part (`GeminiReasoningState.target`):
|
|
54
|
-
//
|
|
55
|
-
// 'thought' a thought summary part: replayed as that thought part;
|
|
56
|
-
// 'function_call' the `functionCall` part of `callId`: the signature is put
|
|
57
|
-
// back on that part when the function call is replayed;
|
|
58
|
-
// 'text' the text part before it: put back on the replayed text.
|
|
59
|
-
//
|
|
60
|
-
// The signature-only reasoning items ('function_call', 'text') carry an empty
|
|
61
|
-
// `summary` — they exist to carry the signature through the tool loop's
|
|
62
|
-
// `replayOutput` hop, which keeps reasoning state and nothing else's.
|
|
63
|
-
//
|
|
64
|
-
// FUNCTION CALL IDS. Gemini 3 names each call; earlier models do not. A call
|
|
65
|
-
// without an id gets a synthetic one (`GEMINI_SYNTHETIC_CALL_ID_PREFIX`) so
|
|
66
|
-
// the neutral contract's non-empty `callId` holds; a synthetic id is never
|
|
67
|
-
// sent back to Gemini (the call and its response are matched by name and
|
|
68
|
-
// order, as Gemini itself does for id-less calls).
|
|
69
|
-
//
|
|
70
|
-
// REFUSED (AI_CAPABILITY_UNSUPPORTED): `previousResponseId` (Gemini stores
|
|
71
|
-
// nothing — the facade refuses it first; this is defence in depth), every
|
|
72
|
-
// hosted tool (`supportsHostedTools: false`), a reasoning effort on a family
|
|
73
|
-
// without thinking, structured output on a family that is not sent a schema,
|
|
74
|
-
// and a schema combined with function tools on a family that rejects that
|
|
75
|
-
// combination.
|
|
76
|
-
//
|
|
77
|
-
// NOT MAPPED: `metadata` (the Gemini API has no free-form request metadata) —
|
|
78
|
-
// dropped. `providerOptions.gemini` is shallow-merged into the CONFIG last as
|
|
79
|
-
// the escape hatch, except `abortSignal`, which belongs to the port.
|
|
80
|
-
// =============================================================================
|
|
81
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
82
|
-
exports.GeminiOutputAssembler = exports.GEMINI_SYNTHETIC_CALL_ID_PREFIX = void 0;
|
|
83
|
-
exports.geminiMimeTypeFromUrl = geminiMimeTypeFromUrl;
|
|
84
|
-
exports.geminiStorageObjectIds = geminiStorageObjectIds;
|
|
85
|
-
exports.geminiReasoningState = geminiReasoningState;
|
|
86
|
-
exports.geminiFunctionResponse = geminiFunctionResponse;
|
|
87
|
-
exports.toGeminiRequest = toGeminiRequest;
|
|
88
|
-
exports.geminiUsage = geminiUsage;
|
|
89
|
-
exports.geminiFinishReason = geminiFinishReason;
|
|
90
|
-
exports.finishGeminiResponse = finishGeminiResponse;
|
|
91
|
-
exports.geminiFallbackResponseId = geminiFallbackResponseId;
|
|
92
|
-
exports.fromGeminiResponse = fromGeminiResponse;
|
|
93
|
-
const node_crypto_1 = require("node:crypto");
|
|
94
|
-
const ai_error_1 = require("../../core/ai-error");
|
|
95
|
-
const structured_output_1 = require("../../core/structured-output");
|
|
96
|
-
const responses_types_1 = require("../../core/types/responses.types");
|
|
97
|
-
const gemini_errors_1 = require("./gemini-errors");
|
|
98
|
-
const gemini_model_catalog_1 = require("./gemini-model-catalog");
|
|
99
|
-
/** The prefix of a call id this adapter made up for an id-less Gemini call. */
|
|
100
|
-
exports.GEMINI_SYNTHETIC_CALL_ID_PREFIX = 'gemini_call_';
|
|
101
|
-
function unsupported(message, details = {}) {
|
|
102
|
-
return new ai_error_1.AiError('AI_CAPABILITY_UNSUPPORTED', message, {
|
|
103
|
-
details: { provider: gemini_errors_1.GEMINI_PROVIDER_ID, ...details },
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
function invalid(message, details = {}) {
|
|
107
|
-
return new ai_error_1.AiError('AI_INVALID_REQUEST', message, {
|
|
108
|
-
details: { provider: gemini_errors_1.GEMINI_PROVIDER_ID, ...details },
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
// ---- request: content -----------------------------------------------------------
|
|
112
|
-
/** `data:<type>;base64,<payload>` → its parts, or `null` for anything else. */
|
|
113
|
-
function parseDataUrl(url) {
|
|
114
|
-
const match = /^data:([^;,]+)(?:;[^,]*)?;base64,(.*)$/s.exec(url);
|
|
115
|
-
return match ? { mimeType: match[1].trim().toLowerCase(), base64: match[2] } : null;
|
|
116
|
-
}
|
|
117
|
-
/** File extensions whose MIME type is unambiguous, for a `fileData` URL (Gemini requires one). */
|
|
118
|
-
const EXTENSION_MIME_TYPES = {
|
|
119
|
-
png: 'image/png',
|
|
120
|
-
jpg: 'image/jpeg',
|
|
121
|
-
jpeg: 'image/jpeg',
|
|
122
|
-
gif: 'image/gif',
|
|
123
|
-
webp: 'image/webp',
|
|
124
|
-
heic: 'image/heic',
|
|
125
|
-
heif: 'image/heif',
|
|
126
|
-
pdf: 'application/pdf',
|
|
127
|
-
txt: 'text/plain',
|
|
128
|
-
md: 'text/md',
|
|
129
|
-
html: 'text/html',
|
|
130
|
-
csv: 'text/csv',
|
|
131
|
-
json: 'application/json',
|
|
132
|
-
mp3: 'audio/mp3',
|
|
133
|
-
wav: 'audio/wav',
|
|
134
|
-
ogg: 'audio/ogg',
|
|
135
|
-
flac: 'audio/flac',
|
|
136
|
-
mp4: 'video/mp4',
|
|
137
|
-
webm: 'video/webm',
|
|
138
|
-
mov: 'video/quicktime',
|
|
139
|
-
};
|
|
140
|
-
/** The MIME type a URL's path extension names, or `undefined` (Gemini then decides). */
|
|
141
|
-
function geminiMimeTypeFromUrl(url) {
|
|
142
|
-
let path;
|
|
143
|
-
try {
|
|
144
|
-
path = new URL(url).pathname;
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
return undefined;
|
|
148
|
-
}
|
|
149
|
-
const ext = /\.([a-z0-9]+)$/i.exec(path)?.[1]?.toLowerCase();
|
|
150
|
-
return ext ? EXTENSION_MIME_TYPES[ext] : undefined;
|
|
151
|
-
}
|
|
152
|
-
function partFromUrl(url) {
|
|
153
|
-
const data = parseDataUrl(url);
|
|
154
|
-
if (data)
|
|
155
|
-
return { inlineData: { mimeType: data.mimeType, data: data.base64 } };
|
|
156
|
-
const mimeType = geminiMimeTypeFromUrl(url);
|
|
157
|
-
return { fileData: { fileUri: url, ...(mimeType ? { mimeType } : {}) } };
|
|
158
|
-
}
|
|
159
|
-
function storagePart(storageObjectId, storage, kind) {
|
|
160
|
-
const delivered = storage?.get(storageObjectId);
|
|
161
|
-
if (!delivered) {
|
|
162
|
-
throw invalid('A storage-object input was not resolved by the runtime.', { part: kind });
|
|
163
|
-
}
|
|
164
|
-
return { inlineData: { mimeType: delivered.mimeType, data: Buffer.from(delivered.data).toString('base64') } };
|
|
165
|
-
}
|
|
166
|
-
function toUserPart(part, storage) {
|
|
167
|
-
switch (part.type) {
|
|
168
|
-
case 'text':
|
|
169
|
-
return { text: part.text };
|
|
170
|
-
case 'image':
|
|
171
|
-
case 'file':
|
|
172
|
-
if (part.url)
|
|
173
|
-
return partFromUrl(part.url);
|
|
174
|
-
if (part.storageObjectId)
|
|
175
|
-
return storagePart(part.storageObjectId, storage, part.type);
|
|
176
|
-
throw invalid(`An ${part.type} part needs a url or a storageObjectId.`, { part: part.type });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
/** Every storage object id `req` names, in order, each once. */
|
|
180
|
-
function geminiStorageObjectIds(req) {
|
|
181
|
-
if (!Array.isArray(req.input))
|
|
182
|
-
return [];
|
|
183
|
-
const ids = new Set();
|
|
184
|
-
for (const item of req.input) {
|
|
185
|
-
if (item.type !== 'message')
|
|
186
|
-
continue;
|
|
187
|
-
for (const part of item.content) {
|
|
188
|
-
if (part.type !== 'text' && !part.url && part.storageObjectId)
|
|
189
|
-
ids.add(part.storageObjectId);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return [...ids];
|
|
193
|
-
}
|
|
194
|
-
/** The Gemini state an item carries, or `undefined` for another provider's / none. */
|
|
195
|
-
function geminiReasoningState(item) {
|
|
196
|
-
const state = item[responses_types_1.AI_PROVIDER_STATE];
|
|
197
|
-
if (!state || state.provider !== gemini_errors_1.GEMINI_PROVIDER_ID)
|
|
198
|
-
return undefined;
|
|
199
|
-
const data = state.data;
|
|
200
|
-
if (!data || typeof data.signature !== 'string' || data.signature.length === 0)
|
|
201
|
-
return undefined;
|
|
202
|
-
if (data.target !== 'thought' && data.target !== 'text' && data.target !== 'function_call')
|
|
203
|
-
return undefined;
|
|
204
|
-
return {
|
|
205
|
-
signature: data.signature,
|
|
206
|
-
target: data.target,
|
|
207
|
-
...(typeof data.callId === 'string' ? { callId: data.callId } : {}),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function isSyntheticCallId(callId) {
|
|
211
|
-
return callId.startsWith(exports.GEMINI_SYNTHETIC_CALL_ID_PREFIX);
|
|
212
|
-
}
|
|
213
|
-
function parseArguments(item) {
|
|
214
|
-
let value;
|
|
215
|
-
try {
|
|
216
|
-
value = JSON.parse(item.arguments || '{}');
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
throw invalid('A replayed function call carries arguments that are not JSON.', { callId: item.callId });
|
|
220
|
-
}
|
|
221
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
222
|
-
throw invalid('A replayed function call carries arguments that are not a JSON object.', { callId: item.callId });
|
|
223
|
-
}
|
|
224
|
-
return value;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* A function's output as Gemini's `functionResponse.response` (a JSON object):
|
|
228
|
-
* a JSON object output is sent as-is; anything else — JSON or plain text — is
|
|
229
|
-
* wrapped as `{ output }`, the key Gemini documents for a function's output.
|
|
230
|
-
*/
|
|
231
|
-
function geminiFunctionResponse(output) {
|
|
232
|
-
try {
|
|
233
|
-
const value = JSON.parse(output);
|
|
234
|
-
if (value && typeof value === 'object' && !Array.isArray(value))
|
|
235
|
-
return value;
|
|
236
|
-
return { output: value };
|
|
237
|
-
}
|
|
238
|
-
catch {
|
|
239
|
-
return { output };
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
/** Appends parts to the conversation, merging into the previous turn when the role matches. */
|
|
243
|
-
function push(conv, role, parts) {
|
|
244
|
-
if (parts.length === 0)
|
|
245
|
-
return;
|
|
246
|
-
const last = conv.contents[conv.contents.length - 1];
|
|
247
|
-
if (last && last.role === role && last.parts) {
|
|
248
|
-
last.parts.push(...parts);
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
conv.contents.push({ role, parts: [...parts] });
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
/** The last text part of the current model turn, if the conversation ends in one. */
|
|
255
|
-
function lastModelTextPart(conv) {
|
|
256
|
-
const last = conv.contents[conv.contents.length - 1];
|
|
257
|
-
if (!last || last.role !== 'model' || !last.parts)
|
|
258
|
-
return undefined;
|
|
259
|
-
for (let i = last.parts.length - 1; i >= 0; i -= 1) {
|
|
260
|
-
const part = last.parts[i];
|
|
261
|
-
if (typeof part.text === 'string' && part.thought !== true)
|
|
262
|
-
return part;
|
|
263
|
-
}
|
|
264
|
-
return undefined;
|
|
265
|
-
}
|
|
266
|
-
function toConversation(req, storage) {
|
|
267
|
-
const conv = { system: req.instructions ? [req.instructions] : [], contents: [] };
|
|
268
|
-
if (typeof req.input === 'string') {
|
|
269
|
-
push(conv, 'user', [{ text: req.input }]);
|
|
270
|
-
return conv;
|
|
271
|
-
}
|
|
272
|
-
const items = req.input;
|
|
273
|
-
/** Signatures waiting for the `functionCall` part of their call, by call id. */
|
|
274
|
-
const callSignatures = new Map();
|
|
275
|
-
items.forEach((item, index) => {
|
|
276
|
-
switch (item.type) {
|
|
277
|
-
case 'message':
|
|
278
|
-
if (item.role === 'system' || item.role === 'developer') {
|
|
279
|
-
if (item.content.some((part) => part.type !== 'text')) {
|
|
280
|
-
throw invalid('A system message may contain only text parts.', { role: item.role });
|
|
281
|
-
}
|
|
282
|
-
conv.system.push(item.content.map((part) => (part.type === 'text' ? part.text : '')).join(''));
|
|
283
|
-
}
|
|
284
|
-
else if (item.role === 'assistant') {
|
|
285
|
-
if (item.content.some((part) => part.type !== 'text')) {
|
|
286
|
-
throw invalid('An assistant message may contain only text parts.', { role: 'assistant' });
|
|
287
|
-
}
|
|
288
|
-
push(conv, 'model', item.content
|
|
289
|
-
.map((part) => (part.type === 'text' ? part.text : ''))
|
|
290
|
-
.filter((text) => text.length > 0)
|
|
291
|
-
.map((text) => ({ text })));
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
push(conv, 'user', item.content.map((part) => toUserPart(part, storage)));
|
|
295
|
-
}
|
|
296
|
-
break;
|
|
297
|
-
case 'function_call': {
|
|
298
|
-
const call = {
|
|
299
|
-
name: item.name,
|
|
300
|
-
args: parseArguments(item),
|
|
301
|
-
...(isSyntheticCallId(item.callId) ? {} : { id: item.callId }),
|
|
302
|
-
};
|
|
303
|
-
const signature = callSignatures.get(item.callId);
|
|
304
|
-
callSignatures.delete(item.callId);
|
|
305
|
-
push(conv, 'model', [{ functionCall: call, ...(signature ? { thoughtSignature: signature } : {}) }]);
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
case 'function_call_output': {
|
|
309
|
-
// Gemini matches a response to its call by NAME: find the call.
|
|
310
|
-
const call = items
|
|
311
|
-
.slice(0, index)
|
|
312
|
-
.reverse()
|
|
313
|
-
.find((earlier) => earlier.type === 'function_call' && earlier.callId === item.callId);
|
|
314
|
-
if (!call) {
|
|
315
|
-
throw invalid('A function_call_output needs its function_call earlier in the input; Gemini stores no conversation.', { callId: item.callId });
|
|
316
|
-
}
|
|
317
|
-
push(conv, 'user', [
|
|
318
|
-
{
|
|
319
|
-
functionResponse: {
|
|
320
|
-
name: call.name,
|
|
321
|
-
response: geminiFunctionResponse(item.output),
|
|
322
|
-
...(isSyntheticCallId(item.callId) ? {} : { id: item.callId }),
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
]);
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
case 'reasoning': {
|
|
329
|
-
const state = geminiReasoningState(item);
|
|
330
|
-
if (!state)
|
|
331
|
-
break;
|
|
332
|
-
if (state.target === 'function_call' && state.callId) {
|
|
333
|
-
callSignatures.set(state.callId, state.signature);
|
|
334
|
-
}
|
|
335
|
-
else if (state.target === 'text') {
|
|
336
|
-
const text = lastModelTextPart(conv);
|
|
337
|
-
if (text && !text.thoughtSignature)
|
|
338
|
-
text.thoughtSignature = state.signature;
|
|
339
|
-
}
|
|
340
|
-
else if (state.target === 'thought') {
|
|
341
|
-
push(conv, 'model', [{ text: item.summary.join('\n\n'), thought: true, thoughtSignature: state.signature }]);
|
|
342
|
-
}
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
return conv;
|
|
348
|
-
}
|
|
349
|
-
// ---- request: tools, schema, thinking ---------------------------------------------
|
|
350
|
-
function toFunctionDeclaration(tool) {
|
|
351
|
-
if (tool.type !== 'function') {
|
|
352
|
-
throw unsupported(`Hosted tool "${tool.type}" is not supported by Gemini.`, { tool: tool.type });
|
|
353
|
-
}
|
|
354
|
-
const schema = (0, structured_output_1.toJsonSchema)(tool.parameters);
|
|
355
|
-
if (schema.type !== 'object') {
|
|
356
|
-
throw invalid(`Tool "${tool.name}" parameters must be an object schema.`, { tool: tool.name });
|
|
357
|
-
}
|
|
358
|
-
return { name: tool.name, description: tool.description, parametersJsonSchema: schema };
|
|
359
|
-
}
|
|
360
|
-
function toToolConfig(choice) {
|
|
361
|
-
const mode = (value) => value;
|
|
362
|
-
if (choice === 'auto')
|
|
363
|
-
return { functionCallingConfig: { mode: mode('AUTO') } };
|
|
364
|
-
if (choice === 'none')
|
|
365
|
-
return { functionCallingConfig: { mode: mode('NONE') } };
|
|
366
|
-
if (choice === 'required')
|
|
367
|
-
return { functionCallingConfig: { mode: mode('ANY') } };
|
|
368
|
-
return { functionCallingConfig: { mode: mode('ANY'), allowedFunctionNames: [choice.name] } };
|
|
369
|
-
}
|
|
370
|
-
/** `req.reasoning` as a `thinkingConfig` for this family — or refused. */
|
|
371
|
-
function toThinkingConfig(req, profile) {
|
|
372
|
-
const effort = req.reasoning?.effort;
|
|
373
|
-
const wantsSummary = req.reasoning?.summary !== undefined;
|
|
374
|
-
if (effort === undefined && !wantsSummary)
|
|
375
|
-
return undefined;
|
|
376
|
-
// An unclassified model is asked with a budget, which every thinking
|
|
377
|
-
// family accepts; an administrator enabled it knowing more than this table,
|
|
378
|
-
// and Gemini's own 400 is then the answer.
|
|
379
|
-
const style = profile?.thinking ?? 'budget';
|
|
380
|
-
if (style === 'none') {
|
|
381
|
-
if (effort !== undefined) {
|
|
382
|
-
throw unsupported(`Model "${req.model}" does not support reasoning effort.`, {
|
|
383
|
-
model: req.model,
|
|
384
|
-
capability: 'reasoning',
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
// A summary asked of a model that does not think: nothing to summarise.
|
|
388
|
-
return undefined;
|
|
389
|
-
}
|
|
390
|
-
const config = { includeThoughts: true };
|
|
391
|
-
if (effort !== undefined) {
|
|
392
|
-
if (style === 'level' && profile?.thinkingLevels) {
|
|
393
|
-
config.thinkingLevel = profile.thinkingLevels[effort];
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
config.thinkingBudget = gemini_model_catalog_1.GEMINI_THINKING_BUDGETS[effort];
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return config;
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Builds the Gemini request for `req`.
|
|
403
|
-
*
|
|
404
|
-
* `profile` is the model's classifier profile (or `null` when unclassified);
|
|
405
|
-
* `storage` is what the adapter delivered for each storage-object part.
|
|
406
|
-
* Throws `AiError` (never an SDK error): see the file header.
|
|
407
|
-
*/
|
|
408
|
-
function toGeminiRequest(req, profile, storage) {
|
|
409
|
-
if (req.previousResponseId !== undefined) {
|
|
410
|
-
throw unsupported('Gemini does not store responses; send the conversation history as input instead.', {
|
|
411
|
-
capability: 'previous_response_id',
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
if (profile?.kind === 'embedding') {
|
|
415
|
-
throw unsupported(`Model "${req.model}" is an embedding model and cannot generate responses.`, {
|
|
416
|
-
model: req.model,
|
|
417
|
-
capability: 'responses',
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
const conv = toConversation(req, storage);
|
|
421
|
-
if (conv.contents.length === 0) {
|
|
422
|
-
throw invalid('The request has no user or assistant content.');
|
|
423
|
-
}
|
|
424
|
-
const declarations = (req.tools ?? []).map(toFunctionDeclaration);
|
|
425
|
-
const config = {};
|
|
426
|
-
if (conv.system.length > 0)
|
|
427
|
-
config.systemInstruction = { parts: [{ text: conv.system.join('\n\n') }] };
|
|
428
|
-
if (declarations.length > 0)
|
|
429
|
-
config.tools = [{ functionDeclarations: declarations }];
|
|
430
|
-
if (req.toolChoice !== undefined)
|
|
431
|
-
config.toolConfig = toToolConfig(req.toolChoice);
|
|
432
|
-
const thinkingConfig = toThinkingConfig(req, profile);
|
|
433
|
-
if (thinkingConfig)
|
|
434
|
-
config.thinkingConfig = thinkingConfig;
|
|
435
|
-
if (req.structuredOutput) {
|
|
436
|
-
if (profile && !profile.structuredOutput) {
|
|
437
|
-
throw unsupported(`Model "${req.model}" does not support structured output.`, {
|
|
438
|
-
model: req.model,
|
|
439
|
-
capability: 'structured_output',
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
if (profile && !profile.structuredWithTools && declarations.length > 0) {
|
|
443
|
-
throw unsupported(`Model "${req.model}" cannot combine structured output with function tools.`, {
|
|
444
|
-
model: req.model,
|
|
445
|
-
capability: 'structured_output',
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
config.responseMimeType = 'application/json';
|
|
449
|
-
config.responseJsonSchema = (0, structured_output_1.toJsonSchema)(req.structuredOutput.schema);
|
|
450
|
-
}
|
|
451
|
-
if (req.maxOutputTokens !== undefined)
|
|
452
|
-
config.maxOutputTokens = req.maxOutputTokens;
|
|
453
|
-
if (req.temperature !== undefined)
|
|
454
|
-
config.temperature = req.temperature;
|
|
455
|
-
const { abortSignal: _signal, ...escapeHatch } = (req.providerOptions?.[gemini_errors_1.GEMINI_PROVIDER_ID] ?? {});
|
|
456
|
-
return { model: req.model, contents: conv.contents, config: { ...config, ...escapeHatch } };
|
|
457
|
-
}
|
|
458
|
-
// ---- response ------------------------------------------------------------------------
|
|
459
|
-
function reasoningItem(summary, state) {
|
|
460
|
-
if (!state)
|
|
461
|
-
return { type: 'reasoning', summary };
|
|
462
|
-
const providerState = { provider: gemini_errors_1.GEMINI_PROVIDER_ID, data: { ...state } };
|
|
463
|
-
return { type: 'reasoning', summary, [responses_types_1.AI_PROVIDER_STATE]: providerState };
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Turns response parts into our output items, in order — incrementally, so
|
|
467
|
-
* the stream mapper can emit deltas as parts arrive, and `create` feeds it a
|
|
468
|
-
* whole response at once. Consecutive text parts are ONE message item and
|
|
469
|
-
* consecutive thought parts ONE reasoning item (a stream splits one logical
|
|
470
|
-
* part across many chunks); a function call closes whatever is open.
|
|
471
|
-
*/
|
|
472
|
-
class GeminiOutputAssembler {
|
|
473
|
-
items = [];
|
|
474
|
-
open = null;
|
|
475
|
-
pendingSignature;
|
|
476
|
-
/** Consumes one part; returns the deltas and completed items it produced. */
|
|
477
|
-
push(part) {
|
|
478
|
-
const events = [];
|
|
479
|
-
if (part.functionCall) {
|
|
480
|
-
events.push(...this.close());
|
|
481
|
-
events.push(...this.functionCall(part.functionCall, part.thoughtSignature));
|
|
482
|
-
return events;
|
|
483
|
-
}
|
|
484
|
-
if (typeof part.text !== 'string') {
|
|
485
|
-
// A signature-only part, or a part kind this adapter never asks for
|
|
486
|
-
// (executable code, inline media output): only its signature matters.
|
|
487
|
-
if (part.thoughtSignature)
|
|
488
|
-
this.attachSignature(part.thoughtSignature);
|
|
489
|
-
return events;
|
|
490
|
-
}
|
|
491
|
-
const kind = part.thought === true ? 'thought' : 'text';
|
|
492
|
-
if (part.text.length === 0) {
|
|
493
|
-
if (part.thoughtSignature)
|
|
494
|
-
this.attachSignature(part.thoughtSignature);
|
|
495
|
-
return events;
|
|
496
|
-
}
|
|
497
|
-
if (!this.open || this.open.kind !== kind) {
|
|
498
|
-
events.push(...this.close());
|
|
499
|
-
this.open = { kind, text: '' };
|
|
500
|
-
if (this.pendingSignature) {
|
|
501
|
-
this.open.signature = this.pendingSignature;
|
|
502
|
-
this.pendingSignature = undefined;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
this.open.text += part.text;
|
|
506
|
-
if (part.thoughtSignature)
|
|
507
|
-
this.open.signature = part.thoughtSignature;
|
|
508
|
-
events.push(kind === 'thought'
|
|
509
|
-
? { type: 'reasoning_summary.delta', delta: part.text }
|
|
510
|
-
: { type: 'output_text.delta', delta: part.text });
|
|
511
|
-
return events;
|
|
512
|
-
}
|
|
513
|
-
/** Closes whatever is open; call once, after the last part. */
|
|
514
|
-
finish() {
|
|
515
|
-
const events = this.close();
|
|
516
|
-
if (this.pendingSignature) {
|
|
517
|
-
events.push(this.done(reasoningItem([], { signature: this.pendingSignature, target: 'text' })));
|
|
518
|
-
this.pendingSignature = undefined;
|
|
519
|
-
}
|
|
520
|
-
return events;
|
|
521
|
-
}
|
|
522
|
-
attachSignature(signature) {
|
|
523
|
-
if (this.open && !this.open.signature) {
|
|
524
|
-
this.open.signature = signature;
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
this.pendingSignature = signature;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
done(item) {
|
|
531
|
-
this.items.push(item);
|
|
532
|
-
return { type: 'output_item.done', item };
|
|
533
|
-
}
|
|
534
|
-
close() {
|
|
535
|
-
const open = this.open;
|
|
536
|
-
if (!open)
|
|
537
|
-
return [];
|
|
538
|
-
this.open = null;
|
|
539
|
-
if (open.kind === 'thought') {
|
|
540
|
-
return [
|
|
541
|
-
this.done(reasoningItem(open.text.length > 0 ? [open.text] : [], open.signature ? { signature: open.signature, target: 'thought' } : undefined)),
|
|
542
|
-
];
|
|
543
|
-
}
|
|
544
|
-
const events = [this.done({ type: 'message', text: open.text })];
|
|
545
|
-
if (open.signature)
|
|
546
|
-
events.push(this.done(reasoningItem([], { signature: open.signature, target: 'text' })));
|
|
547
|
-
return events;
|
|
548
|
-
}
|
|
549
|
-
functionCall(call, signature) {
|
|
550
|
-
const callId = call.id && call.id.length > 0 ? call.id : `${exports.GEMINI_SYNTHETIC_CALL_ID_PREFIX}${(0, node_crypto_1.randomUUID)()}`;
|
|
551
|
-
const args = JSON.stringify(call.args ?? {});
|
|
552
|
-
const events = [];
|
|
553
|
-
const sig = signature ?? this.pendingSignature;
|
|
554
|
-
this.pendingSignature = undefined;
|
|
555
|
-
if (sig)
|
|
556
|
-
events.push(this.done(reasoningItem([], { signature: sig, target: 'function_call', callId })));
|
|
557
|
-
events.push({ type: 'function_call.arguments.delta', callId, delta: args });
|
|
558
|
-
events.push(this.done({ type: 'function_call', callId, name: call.name ?? '', arguments: args }));
|
|
559
|
-
return events;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
exports.GeminiOutputAssembler = GeminiOutputAssembler;
|
|
563
|
-
/** Usage, normalised: output includes thinking tokens, as OpenAI's and Anthropic's do. */
|
|
564
|
-
function geminiUsage(usage) {
|
|
565
|
-
if (!usage)
|
|
566
|
-
return {};
|
|
567
|
-
const out = {};
|
|
568
|
-
const prompt = usage.promptTokenCount;
|
|
569
|
-
const toolPrompt = usage.toolUsePromptTokenCount ?? 0;
|
|
570
|
-
const candidates = usage.candidatesTokenCount;
|
|
571
|
-
const thoughts = usage.thoughtsTokenCount;
|
|
572
|
-
if (typeof prompt === 'number')
|
|
573
|
-
out.inputTokens = prompt + toolPrompt;
|
|
574
|
-
if (typeof candidates === 'number' || typeof thoughts === 'number') {
|
|
575
|
-
out.outputTokens = (candidates ?? 0) + (thoughts ?? 0);
|
|
576
|
-
}
|
|
577
|
-
if (typeof thoughts === 'number')
|
|
578
|
-
out.reasoningTokens = thoughts;
|
|
579
|
-
if (typeof usage.cachedContentTokenCount === 'number')
|
|
580
|
-
out.cachedInputTokens = usage.cachedContentTokenCount;
|
|
581
|
-
return out;
|
|
582
|
-
}
|
|
583
|
-
const CONTENT_FILTER_REASONS = new Set([
|
|
584
|
-
'SAFETY',
|
|
585
|
-
'RECITATION',
|
|
586
|
-
'LANGUAGE',
|
|
587
|
-
'BLOCKLIST',
|
|
588
|
-
'PROHIBITED_CONTENT',
|
|
589
|
-
'SPII',
|
|
590
|
-
'IMAGE_SAFETY',
|
|
591
|
-
'IMAGE_PROHIBITED_CONTENT',
|
|
592
|
-
'IMAGE_RECITATION',
|
|
593
|
-
]);
|
|
594
|
-
const ERROR_REASONS = new Set([
|
|
595
|
-
'MALFORMED_FUNCTION_CALL',
|
|
596
|
-
'UNEXPECTED_TOOL_CALL',
|
|
597
|
-
'TOO_MANY_TOOL_CALLS',
|
|
598
|
-
'OTHER',
|
|
599
|
-
'IMAGE_OTHER',
|
|
600
|
-
'NO_IMAGE',
|
|
601
|
-
]);
|
|
602
|
-
/** Our finish reason for Gemini's (a candidate's `finishReason`, or a blocked prompt). */
|
|
603
|
-
function geminiFinishReason(finishReason, output, promptBlocked = false) {
|
|
604
|
-
if (promptBlocked)
|
|
605
|
-
return 'content_filter';
|
|
606
|
-
const reason = finishReason ?? '';
|
|
607
|
-
if (reason === 'MAX_TOKENS')
|
|
608
|
-
return 'length';
|
|
609
|
-
if (CONTENT_FILTER_REASONS.has(reason))
|
|
610
|
-
return 'content_filter';
|
|
611
|
-
if (ERROR_REASONS.has(reason))
|
|
612
|
-
return 'error';
|
|
613
|
-
// STOP, unspecified, or not yet reported.
|
|
614
|
-
return output.some((item) => item.type === 'function_call') ? 'tool_calls' : 'stop';
|
|
615
|
-
}
|
|
616
|
-
/**
|
|
617
|
-
* The `AiResponse` for mapped parts — shared by `create` and the stream. When
|
|
618
|
-
* the request asked for structured output and the model finished its turn,
|
|
619
|
-
* the text is validated with `parseStructured` (a mismatch throws
|
|
620
|
-
* `AI_STRUCTURED_OUTPUT_INVALID`); a filtered answer to a structured request
|
|
621
|
-
* throws `AI_CONTENT_FILTERED`, since there is nothing to parse.
|
|
622
|
-
*/
|
|
623
|
-
function finishGeminiResponse(parts, request) {
|
|
624
|
-
const outputText = parts.output.map((item) => (item.type === 'message' ? item.text : '')).join('');
|
|
625
|
-
const finishReason = geminiFinishReason(parts.finishReason, parts.output, parts.promptBlocked);
|
|
626
|
-
const result = {
|
|
627
|
-
id: parts.id,
|
|
628
|
-
provider: gemini_errors_1.GEMINI_PROVIDER_ID,
|
|
629
|
-
model: parts.model,
|
|
630
|
-
output: parts.output,
|
|
631
|
-
outputText,
|
|
632
|
-
usage: parts.usage,
|
|
633
|
-
finishReason,
|
|
634
|
-
};
|
|
635
|
-
if (request.structuredOutput && finishReason !== 'tool_calls') {
|
|
636
|
-
if (finishReason === 'content_filter') {
|
|
637
|
-
throw new ai_error_1.AiError('AI_CONTENT_FILTERED', 'Gemini blocked the response to a structured-output request.', {
|
|
638
|
-
details: { provider: gemini_errors_1.GEMINI_PROVIDER_ID },
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
result.parsed = (0, structured_output_1.parseStructured)(request.structuredOutput.schema, outputText);
|
|
642
|
-
}
|
|
643
|
-
return result;
|
|
644
|
-
}
|
|
645
|
-
/** An id for a response Gemini did not name. */
|
|
646
|
-
function geminiFallbackResponseId() {
|
|
647
|
-
return `gemini-${(0, node_crypto_1.randomUUID)()}`;
|
|
648
|
-
}
|
|
649
|
-
/** Maps a finished `GenerateContentResponse` to an `AiResponse`. */
|
|
650
|
-
function fromGeminiResponse(response, request) {
|
|
651
|
-
const candidate = response.candidates?.[0];
|
|
652
|
-
const assembler = new GeminiOutputAssembler();
|
|
653
|
-
for (const part of candidate?.content?.parts ?? [])
|
|
654
|
-
assembler.push(part);
|
|
655
|
-
assembler.finish();
|
|
656
|
-
return finishGeminiResponse({
|
|
657
|
-
id: response.responseId || geminiFallbackResponseId(),
|
|
658
|
-
model: response.modelVersion || request.model,
|
|
659
|
-
output: assembler.items,
|
|
660
|
-
finishReason: candidate?.finishReason,
|
|
661
|
-
promptBlocked: !candidate && Boolean(response.promptFeedback?.blockReason),
|
|
662
|
-
usage: geminiUsage(response.usageMetadata),
|
|
663
|
-
}, request);
|
|
664
|
-
}
|
|
665
|
-
//# sourceMappingURL=gemini-content.mapper.js.map
|