@xandreed/plugin-models 0.13.0 → 0.15.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 (151) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/anthropicOAuth.d.ts +45 -11
  3. package/dist/auth/anthropicOAuth.js +23 -23
  4. package/dist/auth/auth-store.conformance.d.ts +63 -0
  5. package/dist/auth/auth-store.conformance.js +152 -0
  6. package/dist/auth/credential-input.entity.d.ts +52 -0
  7. package/dist/auth/credential-input.entity.functions.d.ts +18 -0
  8. package/dist/auth/credential-input.entity.functions.js +12 -0
  9. package/dist/auth/credential-input.entity.js +22 -0
  10. package/dist/auth/credential-refresh.entity.d.ts +17 -0
  11. package/dist/auth/credential-refresh.entity.functions.d.ts +57 -0
  12. package/dist/auth/credential-refresh.entity.functions.js +21 -0
  13. package/dist/auth/credential-refresh.entity.js +2 -0
  14. package/dist/auth/localAuth.d.ts +12 -2
  15. package/dist/auth/localAuth.js +58 -86
  16. package/dist/auth/memory-auth.adapter.d.ts +35 -0
  17. package/dist/auth/memory-auth.adapter.js +35 -0
  18. package/dist/auth/openAiCodexOAuth.d.ts +28 -6
  19. package/dist/auth/openAiCodexOAuth.js +22 -32
  20. package/dist/auth/token-response.entity.d.ts +21 -0
  21. package/dist/auth/token-response.entity.functions.d.ts +20 -0
  22. package/dist/auth/token-response.entity.functions.js +10 -0
  23. package/dist/auth/token-response.entity.js +10 -0
  24. package/dist/compat/index.d.ts +2 -0
  25. package/dist/compat/index.js +1 -0
  26. package/dist/conformance/index.d.ts +6 -0
  27. package/dist/conformance/index.js +3 -0
  28. package/dist/index.d.ts +30 -13
  29. package/dist/index.js +25 -50
  30. package/dist/llm/chat-wire.entity.d.ts +123 -0
  31. package/dist/llm/chat-wire.entity.functions.d.ts +28 -0
  32. package/dist/llm/chat-wire.entity.functions.js +10 -0
  33. package/dist/llm/chat-wire.entity.js +37 -0
  34. package/dist/llm/compat-telemetry.adapter.d.ts +6 -0
  35. package/dist/llm/compat-telemetry.adapter.js +21 -0
  36. package/dist/llm/compat.d.ts +84 -33
  37. package/dist/llm/compat.js +188 -121
  38. package/dist/llm/model-completion.adapter.d.ts +49 -0
  39. package/dist/llm/model-completion.adapter.js +77 -0
  40. package/dist/llm/model-completion.entity.d.ts +4 -0
  41. package/dist/llm/model-completion.entity.functions.d.ts +3 -0
  42. package/dist/llm/model-completion.entity.functions.js +7 -0
  43. package/dist/llm/model-completion.entity.js +7 -0
  44. package/dist/llm/model-retry-policy.entity.d.ts +37 -0
  45. package/dist/llm/model-retry-policy.entity.functions.d.ts +16 -0
  46. package/dist/llm/model-retry-policy.entity.functions.js +33 -0
  47. package/dist/llm/model-retry-policy.entity.js +23 -0
  48. package/dist/llm/model-settings.entity.d.ts +85 -0
  49. package/dist/llm/model-settings.entity.functions.d.ts +11 -0
  50. package/dist/llm/model-settings.entity.functions.js +49 -0
  51. package/dist/llm/model-settings.entity.js +28 -0
  52. package/dist/llm/model-usage.entity.d.ts +61 -0
  53. package/dist/llm/model-usage.entity.functions.d.ts +11 -0
  54. package/dist/llm/model-usage.entity.functions.js +24 -0
  55. package/dist/llm/model-usage.entity.js +19 -0
  56. package/dist/llm/modelCatalog.d.ts +47 -7
  57. package/dist/llm/modelCatalog.js +31 -57
  58. package/dist/llm/openAiCodex.d.ts +18 -7
  59. package/dist/llm/openAiCodex.js +57 -45
  60. package/dist/llm/openAiCodexWebSocket.d.ts +60 -7
  61. package/dist/llm/openAiCodexWebSocket.js +113 -105
  62. package/dist/llm/prompt-cache-frame.entity.d.ts +18 -0
  63. package/dist/llm/prompt-cache-frame.entity.js +10 -0
  64. package/dist/llm/prompt-cache.entity.d.ts +57 -0
  65. package/dist/llm/prompt-cache.entity.functions.d.ts +8 -0
  66. package/dist/llm/prompt-cache.entity.functions.js +73 -0
  67. package/dist/llm/prompt-cache.entity.js +27 -0
  68. package/dist/llm/provider-client.adapter.d.ts +9 -0
  69. package/dist/llm/provider-client.adapter.js +60 -0
  70. package/dist/llm/provider-failure.entity.d.ts +42 -0
  71. package/dist/llm/provider-failure.entity.functions.d.ts +8 -0
  72. package/dist/llm/provider-failure.entity.functions.js +67 -0
  73. package/dist/llm/provider-failure.entity.js +20 -0
  74. package/dist/llm/provider-profile.entity.d.ts +79 -0
  75. package/dist/llm/provider-profile.entity.functions.d.ts +23 -0
  76. package/dist/llm/provider-profile.entity.functions.js +83 -0
  77. package/dist/llm/provider-profile.entity.js +10 -0
  78. package/dist/llm/providers.d.ts +125 -29
  79. package/dist/llm/providers.js +311 -117
  80. package/dist/llm/retry.d.ts +11 -47
  81. package/dist/llm/retry.js +33 -52
  82. package/dist/llm/router.d.ts +56 -28
  83. package/dist/llm/router.js +125 -88
  84. package/dist/llm/sse.d.ts +58 -29
  85. package/dist/llm/sse.js +47 -39
  86. package/dist/llm/thinking-binding.entity.d.ts +49 -0
  87. package/dist/llm/thinking-binding.entity.functions.d.ts +21 -0
  88. package/dist/llm/thinking-binding.entity.functions.js +16 -0
  89. package/dist/llm/thinking-binding.entity.js +11 -0
  90. package/dist/llm/utilityLlm.d.ts +14 -5
  91. package/dist/llm/utilityLlm.js +18 -13
  92. package/dist/plugin.adapter.d.ts +154 -6
  93. package/dist/plugin.adapter.js +53 -10
  94. package/dist/ports/auth-store.port.d.ts +73 -0
  95. package/dist/ports/auth-store.port.js +24 -0
  96. package/dist/ports/host-credentials.port.d.ts +22 -0
  97. package/dist/ports/host-credentials.port.js +4 -0
  98. package/dist/ports/model-call-completion.port.d.ts +9 -0
  99. package/dist/ports/model-call-completion.port.js +2 -0
  100. package/dist/ports/model-endpoints.port.d.ts +66 -19
  101. package/dist/ports/model-endpoints.port.js +22 -4
  102. package/dist/ports/model-retry-policy.port.d.ts +17 -0
  103. package/dist/ports/model-retry-policy.port.js +3 -0
  104. package/dist/ports/model-settings.port.d.ts +32 -0
  105. package/dist/ports/model-settings.port.js +4 -0
  106. package/dist/ports/models-transport.port.d.ts +53 -0
  107. package/dist/ports/models-transport.port.js +7 -0
  108. package/dist/ports/prompt-cache-frame.port.d.ts +21 -0
  109. package/dist/ports/prompt-cache-frame.port.js +4 -0
  110. package/dist/ports/settings-store.port.d.ts +70 -0
  111. package/dist/ports/settings-store.port.js +33 -0
  112. package/dist/ports/thinking-binding.port.d.ts +7 -0
  113. package/dist/ports/thinking-binding.port.js +4 -0
  114. package/dist/settings/localSettings.d.ts +2 -1
  115. package/dist/settings/localSettings.js +18 -61
  116. package/dist/settings/memory-settings.adapter.d.ts +12 -0
  117. package/dist/settings/memory-settings.adapter.js +11 -0
  118. package/dist/settings/roleView.d.ts +3 -1
  119. package/dist/settings/roleView.js +1 -1
  120. package/dist/settings/settings-store.conformance.d.ts +47 -0
  121. package/dist/settings/settings-store.conformance.js +100 -0
  122. package/dist/settings/settings-value.entity.d.ts +15 -0
  123. package/dist/settings/settings-value.entity.functions.d.ts +51 -0
  124. package/dist/settings/settings-value.entity.functions.js +42 -0
  125. package/dist/settings/settings-value.entity.js +2 -0
  126. package/dist/storage/json-file.d.ts +18 -0
  127. package/dist/storage/json-file.js +26 -0
  128. package/dist/transport/http-proxy.entity.d.ts +104 -0
  129. package/dist/transport/http-proxy.entity.functions.d.ts +51 -0
  130. package/dist/transport/http-proxy.entity.functions.js +112 -0
  131. package/dist/transport/http-proxy.entity.js +42 -0
  132. package/dist/transport/model-request.conformance.d.ts +13 -0
  133. package/dist/transport/model-request.conformance.js +36 -0
  134. package/dist/transport/models-transport.adapter.d.ts +49 -0
  135. package/dist/transport/models-transport.adapter.js +99 -0
  136. package/dist/transport/resilient-http-client.adapter.d.ts +73 -0
  137. package/dist/transport/resilient-http-client.adapter.js +177 -0
  138. package/dist/transport/undici-fetch.adapter.d.ts +13 -0
  139. package/dist/transport/undici-fetch.adapter.js +1 -0
  140. package/package.json +13 -6
  141. package/dist/auth/anthropicOAuth.test.d.ts +0 -1
  142. package/dist/auth/localAuth.test.d.ts +0 -1
  143. package/dist/llm/compat.test.d.ts +0 -1
  144. package/dist/llm/modelCatalog.test.d.ts +0 -1
  145. package/dist/llm/providers.test.d.ts +0 -1
  146. package/dist/llm/retry.test.d.ts +0 -1
  147. package/dist/llm/router.test.d.ts +0 -1
  148. package/dist/llm/sse.test.d.ts +0 -1
  149. package/dist/plugin.adapter.test.d.ts +0 -1
  150. package/dist/settings/localSettings.test.d.ts +0 -1
  151. package/dist/settings/roleView.test.d.ts +0 -1
@@ -0,0 +1,123 @@
1
+ import { Schema } from "effect";
2
+ /** The usage object OpenAI-compatible completions carry (streaming sends it
3
+ * on the last chunk under `stream_options: {include_usage: true}`). */
4
+ export declare const CompletionUsage: Schema.Struct<{
5
+ readonly prompt_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
6
+ readonly completion_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
7
+ readonly total_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
8
+ /** DeepSeek-style cache accounting. */
9
+ readonly prompt_cache_hit_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
10
+ /** Moonshot-style cache accounting. */
11
+ readonly cached_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
12
+ /** OpenAI-native cache reads; gateways (OpenCode's among them) add the cache writes. */
13
+ readonly prompt_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
14
+ readonly cached_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
15
+ readonly cache_write_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
16
+ }>>>;
17
+ /** OpenAI-native reasoning accounting, counted inside `completion_tokens`. */
18
+ readonly completion_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
19
+ readonly reasoning_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
20
+ }>>>;
21
+ }>;
22
+ export type CompletionUsage = typeof CompletionUsage.Type;
23
+ /** One non-streaming chat completion. */
24
+ export declare const ChatCompletion: Schema.Struct<{
25
+ readonly choices: Schema.optionalKey<Schema.NullOr<Schema.$Array<Schema.Struct<{
26
+ readonly finish_reason: Schema.optionalKey<Schema.NullOr<Schema.String>>;
27
+ readonly message: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
28
+ readonly tool_calls: Schema.optionalKey<Schema.NullOr<Schema.$Array<Schema.Struct<{
29
+ readonly id: Schema.optionalKey<Schema.NullOr<Schema.brand<Schema.String, "ToolCallId">>>;
30
+ readonly function: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
31
+ readonly name: Schema.optionalKey<Schema.NullOr<Schema.String>>;
32
+ readonly arguments: Schema.optionalKey<Schema.NullOr<Schema.String>>;
33
+ }>>>;
34
+ }>>>>;
35
+ readonly content: Schema.optionalKey<Schema.NullOr<Schema.String>>;
36
+ readonly reasoning_content: Schema.optionalKey<Schema.NullOr<Schema.String>>;
37
+ readonly reasoning: Schema.optionalKey<Schema.NullOr<Schema.String>>;
38
+ }>>>;
39
+ }>>>>;
40
+ readonly usage: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
41
+ readonly prompt_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
42
+ readonly completion_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
43
+ readonly total_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
44
+ /** DeepSeek-style cache accounting. */
45
+ readonly prompt_cache_hit_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
46
+ /** Moonshot-style cache accounting. */
47
+ readonly cached_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
48
+ /** OpenAI-native cache reads; gateways (OpenCode's among them) add the cache writes. */
49
+ readonly prompt_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
50
+ readonly cached_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
51
+ readonly cache_write_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
52
+ }>>>;
53
+ /** OpenAI-native reasoning accounting, counted inside `completion_tokens`. */
54
+ readonly completion_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
55
+ readonly reasoning_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
56
+ }>>>;
57
+ }>>>;
58
+ }>;
59
+ export type ChatCompletion = typeof ChatCompletion.Type;
60
+ /** A streamed tool-call fragment: fragments of one call share its `index`. */
61
+ export declare const StreamToolCall: Schema.Struct<{
62
+ readonly index: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
63
+ readonly id: Schema.optionalKey<Schema.NullOr<Schema.brand<Schema.String, "ToolCallId">>>;
64
+ readonly function: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
65
+ readonly name: Schema.optionalKey<Schema.NullOr<Schema.String>>;
66
+ readonly arguments: Schema.optionalKey<Schema.NullOr<Schema.String>>;
67
+ }>>>;
68
+ }>;
69
+ export type StreamToolCall = typeof StreamToolCall.Type;
70
+ /** One `data:` chunk of a streaming chat completion. */
71
+ export declare const StreamChunk: Schema.Struct<{
72
+ readonly choices: Schema.optionalKey<Schema.NullOr<Schema.$Array<Schema.Struct<{
73
+ readonly finish_reason: Schema.optionalKey<Schema.NullOr<Schema.String>>;
74
+ readonly delta: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
75
+ readonly tool_calls: Schema.optionalKey<Schema.NullOr<Schema.$Array<Schema.Struct<{
76
+ readonly index: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
77
+ readonly id: Schema.optionalKey<Schema.NullOr<Schema.brand<Schema.String, "ToolCallId">>>;
78
+ readonly function: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
79
+ readonly name: Schema.optionalKey<Schema.NullOr<Schema.String>>;
80
+ readonly arguments: Schema.optionalKey<Schema.NullOr<Schema.String>>;
81
+ }>>>;
82
+ }>>>>;
83
+ readonly content: Schema.optionalKey<Schema.NullOr<Schema.String>>;
84
+ readonly reasoning_content: Schema.optionalKey<Schema.NullOr<Schema.String>>;
85
+ readonly reasoning: Schema.optionalKey<Schema.NullOr<Schema.String>>;
86
+ }>>>;
87
+ }>>>>;
88
+ readonly usage: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
89
+ readonly prompt_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
90
+ readonly completion_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
91
+ readonly total_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
92
+ /** DeepSeek-style cache accounting. */
93
+ readonly prompt_cache_hit_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
94
+ /** Moonshot-style cache accounting. */
95
+ readonly cached_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
96
+ /** OpenAI-native cache reads; gateways (OpenCode's among them) add the cache writes. */
97
+ readonly prompt_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
98
+ readonly cached_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
99
+ readonly cache_write_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
100
+ }>>>;
101
+ /** OpenAI-native reasoning accounting, counted inside `completion_tokens`. */
102
+ readonly completion_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
103
+ readonly reasoning_tokens: Schema.optionalKey<Schema.NullOr<Schema.Number>>;
104
+ }>>>;
105
+ }>>>;
106
+ readonly error: Schema.optionalKey<Schema.Unknown>;
107
+ }>;
108
+ export type StreamChunk = typeof StreamChunk.Type;
109
+ /** The OpenAI error object a gateway reports a failure with: its type, its code and its message. */
110
+ export declare const GatewayError: Schema.Struct<{
111
+ readonly type: Schema.optionalKey<Schema.NullOr<Schema.String>>;
112
+ readonly code: Schema.optionalKey<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Number]>>>;
113
+ readonly message: Schema.optionalKey<Schema.NullOr<Schema.String>>;
114
+ }>;
115
+ export type GatewayError = typeof GatewayError.Type;
116
+ /** A failed request's body in the OpenAI error shape. */
117
+ export declare const GatewayErrorBody: Schema.Struct<{
118
+ readonly error: Schema.Struct<{
119
+ readonly type: Schema.optionalKey<Schema.NullOr<Schema.String>>;
120
+ readonly code: Schema.optionalKey<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Number]>>>;
121
+ readonly message: Schema.optionalKey<Schema.NullOr<Schema.String>>;
122
+ }>;
123
+ }>;
@@ -0,0 +1,28 @@
1
+ import { AiError } from "effect/ai";
2
+ import { Effect, Option, Schema } from "effect";
3
+ import { ChatCompletion, GatewayError, StreamChunk } from "./chat-wire.entity.js";
4
+ /** A completion body, decoded; one that is no JSON or of the wrong shape is the provider's `InvalidResponseError`. */
5
+ export declare const decodeChatCompletion: (moduleName: string, text: string) => Effect.Effect<ChatCompletion, AiError.AiError>;
6
+ /** A `data:` line's chunk, decoded; one that is no JSON object of the chunk's shape (`null` included) is the provider's `InvalidResponseError`. */
7
+ export declare const decodeStreamChunk: (moduleName: string, payload: string) => Effect.Effect<StreamChunk, AiError.AiError>;
8
+ /** A tool call's arguments, parsed: the model's own output, so a failure is the caller's to name. */
9
+ export declare const decodeToolArguments: (text: string) => Effect.Effect<Schema.Json, Schema.SchemaError>;
10
+ /** The OpenAI error object of a failed request's body, when it has one. */
11
+ export declare const gatewayErrorOf: (body: string) => Option.Option<GatewayError>;
12
+ /** The OpenAI error object of a stream's error chunk, when it is one. */
13
+ export declare const chunkErrorOf: (error: unknown) => Option.Option<GatewayError>;
14
+ /**
15
+ * The error types and codes gateways report an exhausted balance or quota
16
+ * with: OpenCode's `CreditsError`, OpenAI's `insufficient_quota` and
17
+ * `billing_insufficient_balance`, Moonshot's `exceeded_current_quota_error`.
18
+ */
19
+ export declare const QUOTA_ERRORS: ReadonlySet<string>;
20
+ /**
21
+ * Whether a failure is an exhausted balance or quota, which no retry and no
22
+ * fallback model fixes: a 402 (Payment Required), or an error whose type or
23
+ * code names one (`QUOTA_ERRORS`), at any status. A plain 429 is not: it is
24
+ * a rate limit, retried. The message text is not read.
25
+ */
26
+ export declare const isQuotaExhausted: (status: Option.Option<number>, error: Option.Option<GatewayError>) => boolean;
27
+ /** Whether a gateway rejected the selected model (OpenCode's `ModelError`), whatever status it answered with. */
28
+ export declare const isModelRejection: (error: GatewayError) => boolean;
@@ -0,0 +1,10 @@
1
+ import { AiError } from "effect/ai";
2
+ import { Effect, Option, Schema } from "effect";
3
+ import { ChatCompletion, GatewayError, GatewayErrorBody, StreamChunk } from "./chat-wire.entity.js";
4
+ const invalidResponse = (moduleName, method, what) => (error) => AiError.make({ module: moduleName, method, reason: new AiError.InvalidResponseError({ description: `${what}: ${error.message}` }) }), completion = Schema.decodeUnknownEffect(Schema.fromJsonString(ChatCompletion)), chunk = Schema.decodeUnknownEffect(Schema.fromJsonString(StreamChunk)), json = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Json)), errorBody = Schema.decodeUnknownOption(Schema.fromJsonString(GatewayErrorBody)), errorObject = Schema.decodeUnknownOption(GatewayError);
5
+ export const decodeChatCompletion = (moduleName, text) => completion(text).pipe(Effect.mapError(invalidResponse(moduleName, "generateText", "the completion body is not a chat completion"))), decodeStreamChunk = (moduleName, payload) => chunk(payload).pipe(Effect.mapError(invalidResponse(moduleName, "streamText", `a data: line is not a completion chunk (${payload.slice(0, 200)})`))), decodeToolArguments = (text) => json(text), gatewayErrorOf = (body) => Option.map(errorBody(body), (decoded) => decoded.error), chunkErrorOf = (error) => errorObject(error), QUOTA_ERRORS = new Set([
6
+ "CreditsError",
7
+ "insufficient_quota",
8
+ "billing_insufficient_balance",
9
+ "exceeded_current_quota_error"
10
+ ]), isQuotaExhausted = (status, error) => Option.contains(status, 402) || Option.exists(error, (found) => [found.type, found.code].some((name) => typeof name === "string" && QUOTA_ERRORS.has(name))), isModelRejection = (error) => error.type === "ModelError" || /model .*not supported/i.test(error.message ?? "");
@@ -0,0 +1,37 @@
1
+ import { Schema } from "effect";
2
+ import { ToolCallId } from "@xandreed/core";
3
+ const Count = Schema.optionalKey(Schema.NullOr(Schema.Number)), Text = Schema.optionalKey(Schema.NullOr(Schema.String));
4
+ export const CompletionUsage = Schema.Struct({
5
+ prompt_tokens: Count,
6
+ completion_tokens: Count,
7
+ total_tokens: Count,
8
+ prompt_cache_hit_tokens: Count,
9
+ cached_tokens: Count,
10
+ prompt_tokens_details: Schema.optionalKey(Schema.NullOr(Schema.Struct({ cached_tokens: Count, cache_write_tokens: Count }))),
11
+ completion_tokens_details: Schema.optionalKey(Schema.NullOr(Schema.Struct({ reasoning_tokens: Count })))
12
+ });
13
+ const WireToolCallId = Schema.optionalKey(Schema.NullOr(ToolCallId)), ToolCallFunction = Schema.optionalKey(Schema.NullOr(Schema.Struct({ name: Text, arguments: Text }))), MessageText = { content: Text, reasoning_content: Text, reasoning: Text };
14
+ export const ChatCompletion = Schema.Struct({
15
+ choices: Schema.optionalKey(Schema.NullOr(Schema.Array(Schema.Struct({
16
+ finish_reason: Text,
17
+ message: Schema.optionalKey(Schema.NullOr(Schema.Struct({
18
+ ...MessageText,
19
+ tool_calls: Schema.optionalKey(Schema.NullOr(Schema.Array(Schema.Struct({ id: WireToolCallId, function: ToolCallFunction }))))
20
+ })))
21
+ })))),
22
+ usage: Schema.optionalKey(Schema.NullOr(CompletionUsage))
23
+ }), StreamToolCall = Schema.Struct({ index: Count, id: WireToolCallId, function: ToolCallFunction }), StreamChunk = Schema.Struct({
24
+ choices: Schema.optionalKey(Schema.NullOr(Schema.Array(Schema.Struct({
25
+ finish_reason: Text,
26
+ delta: Schema.optionalKey(Schema.NullOr(Schema.Struct({
27
+ ...MessageText,
28
+ tool_calls: Schema.optionalKey(Schema.NullOr(Schema.Array(StreamToolCall)))
29
+ })))
30
+ })))),
31
+ usage: Schema.optionalKey(Schema.NullOr(CompletionUsage)),
32
+ error: Schema.optionalKey(Schema.Unknown)
33
+ }), GatewayError = Schema.Struct({
34
+ type: Text,
35
+ code: Schema.optionalKey(Schema.NullOr(Schema.Union([Schema.String, Schema.Number]))),
36
+ message: Text
37
+ }), GatewayErrorBody = Schema.Struct({ error: GatewayError });
@@ -0,0 +1,6 @@
1
+ import { Effect } from "effect";
2
+ import type { Tracer } from "effect";
3
+ /** Annotate the actual native operation, never a routing or engine wrapper. */
4
+ export declare const annotateCompatRequest: (span: Tracer.Span, provider: string, model: string) => Effect.Effect<void>;
5
+ /** Raw cumulative fields replace their own attributes; missing counts never become zero. */
6
+ export declare const observeCompatUsage: (span: Tracer.Span, raw: unknown, final: boolean, reasoning?: boolean) => Effect.Effect<void>;
@@ -0,0 +1,21 @@
1
+ import { Effect, Option } from "effect";
2
+ import { providerUsageCounts } from "./model-usage.entity.functions.js";
3
+ export const annotateCompatRequest = (span, provider, model) => Effect.sync(() => {
4
+ span.attribute("gen_ai.provider.name", provider);
5
+ span.attribute("gen_ai.request.model", model);
6
+ span.attribute("gen_ai.operation.name", "chat");
7
+ }), observeCompatUsage = (span, raw, final, reasoning = !0) => Option.match(providerUsageCounts("compat", raw, reasoning), {
8
+ onNone: () => Effect.void,
9
+ onSome: (counts) => Effect.sync(() => {
10
+ [
11
+ ["gen_ai.usage.input_tokens", counts.inputTokens],
12
+ ["gen_ai.usage.output_tokens", !final && counts.outputTokens === 0 ? void 0 : counts.outputTokens],
13
+ ["gen_ai.usage.cache_read.input_tokens", counts.cacheReadTokens],
14
+ ["gen_ai.usage.cache_write.input_tokens", counts.cacheWriteTokens],
15
+ ["gen_ai.usage.reasoning.output_tokens", counts.reasoningTokens]
16
+ ].forEach(([name, value]) => {
17
+ if (value !== void 0 && Number.isSafeInteger(value))
18
+ span.attribute(name, value);
19
+ });
20
+ })
21
+ });
@@ -1,7 +1,7 @@
1
- import { AiError, LanguageModel, Tool } from "effect/ai";
2
- import type { Prompt } from "effect/ai";
3
- import { Effect } from "effect";
4
- import type { CompletionUsage } from "./sse.js";
1
+ import { AiError, LanguageModel, Prompt, Tool, type Response as ResponseParts } from "effect/ai";
2
+ import { Effect, Schema } from "effect";
3
+ import type { UsageReading } from "./sse.js";
4
+ import type { ModelsFetch } from "../ports/models-transport.port.js";
5
5
  /**
6
6
  * A generic OpenAI-compatible `/chat/completions` `LanguageModel` over raw
7
7
  * `fetch`. The official `@effect/ai-openai` client targets api.openai.com;
@@ -10,10 +10,11 @@ import type { CompletionUsage } from "./sse.js";
10
10
  *
11
11
  * `generateText` consumes whole turns (`stream: false`); `streamText` runs
12
12
  * the same request with `stream: true` through the pure SSE state machine in
13
- * `sse.ts`. Errors BEFORE any stream part surface with the same taxonomy as
14
- * `generateText` — that boundary is what the retry gate (router) keys on.
13
+ * `sse.ts`. Both paths use the same bounded request policy before handing
14
+ * off a successful response; body and SSE failures after handoff are final.
15
15
  */
16
16
  type Json = Record<string, unknown>;
17
+ /** @public */
17
18
  export interface CompatConfig {
18
19
  /** Module name for `AiError` provenance (e.g. "OpenCode"). */
19
20
  readonly moduleName: string;
@@ -32,16 +33,55 @@ export interface CompatConfig {
32
33
  * thinking defaults or call policy — for endpoints that reject the
33
34
  * gateway extensions (Gemini's OpenAI-compatible API). */
34
35
  readonly standardOnly?: boolean;
35
- /** Injectable for tests; defaults to global fetch. */
36
- readonly fetchImpl?: typeof fetch;
37
- /** Host-owned admission (e.g. reserve a worst-case cost) before every call. */
36
+ /** How the endpoint's usage is read (default: every count, reasoning
37
+ * included; see `UsageReading`). */
38
+ readonly usage?: UsageReading;
39
+ /** The model reads images: send them as `image_url` parts (default false: label lines; see `toChatMessages`). */
40
+ readonly images?: boolean;
41
+ /** The fetch requests go through (the models plugin supplies its chosen transport); otherwise uses the call-local FetchHttpClient.Fetch reference. */
42
+ readonly fetchImpl?: ModelsFetch;
43
+ /** Host-owned admission (e.g. reserve a worst-case cost) before every
44
+ * physical send: the first and each retry the request policy makes. A
45
+ * failure ends the call without that send. */
38
46
  readonly beforeRequest?: (body: Readonly<Record<string, unknown>>) => Effect.Effect<void, AiError.AiError>;
39
- /** Per-call accounting/resource boundary. Non-streaming responses are fully
40
- * buffered inside request so interruption also aborts body consumption. */
47
+ /** Per-call accounting/resource boundary around the whole logical call,
48
+ * its retries included: it sees the final response or failure once.
49
+ * Non-streaming responses are fully buffered inside request so
50
+ * interruption also aborts body consumption. */
41
51
  readonly aroundRequest?: (body: Readonly<Record<string, unknown>>, request: Effect.Effect<Response, AiError.AiError>) => Effect.Effect<Response, AiError.AiError>;
42
52
  }
43
- /** Decoded `Prompt` messages → chat-completions message objects. */
44
- export declare const toChatMessages: (prompt: Prompt.Prompt) => ReadonlyArray<Json>;
53
+ /**
54
+ * How `toChatMessages` writes a request.
55
+ *
56
+ * @public
57
+ */
58
+ export interface ChatMessageOptions {
59
+ /** Send images as `image_url` parts (default false: label lines). */
60
+ readonly images?: boolean;
61
+ }
62
+ /**
63
+ * Decoded `Prompt` messages → chat-completions message objects. A
64
+ * text-only user message is its text, as before. Without `images`, a user
65
+ * message is one string: its text, then a label line per file part, and a
66
+ * tool result is its text with a label line per image. With `images`, a
67
+ * user message with images is a content array of its parts in order (an
68
+ * image as an `image_url` part with a `data:` URL), and a tool result is its
69
+ * text in the tool message, while its images, which a tool message cannot
70
+ * carry, follow all the batch's tool messages in one user message, each
71
+ * result's captioned `Images returned by <tool> (call <id>):`; a user
72
+ * message right after the batch is merged into it, so no two user messages
73
+ * follow each other, captioned `The user's message:` when it has images of
74
+ * its own.
75
+ */
76
+ export declare const toChatMessages: (prompt: Prompt.Prompt, options?: ChatMessageOptions) => ReadonlyArray<Json>;
77
+ /**
78
+ * A prompt with every file part a label line: what a route that reads no
79
+ * images is sent (the models plugin rewrites a request so on such routes).
80
+ * A user message with file parts becomes one text part (its text, then a
81
+ * label line each); a tool result with model output becomes its text with a
82
+ * label line per file part, as its result. Messages without files are kept.
83
+ */
84
+ export declare const promptWithImageLabels: (prompt: Prompt.Prompt) => Prompt.Prompt;
45
85
  /** User-defined tools → chat-completions function declarations (empty when
46
86
  * the request carries none — the caller omits the `tools` key entirely). */
47
87
  export declare const toChatTools: (tools: ReadonlyArray<Tool.Any>) => ReadonlyArray<Json>;
@@ -54,28 +94,39 @@ export declare const toChatTools: (tools: ReadonlyArray<Tool.Any>) => ReadonlyAr
54
94
  * `thinking: {type: "enabled"}`; qwen takes `enable_thinking`. Disabling is
55
95
  * never sent (kimi-k2.7+ rejects it outright); unknown families get nothing.
56
96
  */
57
- export declare const thinkingParams: (model: string) => Record<string, unknown>;
58
- interface ChatCompletion {
59
- readonly choices?: ReadonlyArray<{
60
- readonly finish_reason?: string;
97
+ export declare const thinkingParams: (model: string) => Schema.JsonObject;
98
+ /** Parse one non-streaming completion into `@effect/ai` encoded parts. */
99
+ export declare const fromChatCompletion: (moduleName: string, body: {
100
+ readonly usage?: {
101
+ readonly prompt_tokens_details?: {
102
+ readonly cached_tokens?: number | null;
103
+ readonly cache_write_tokens?: number | null;
104
+ } | null;
105
+ readonly cached_tokens?: number | null;
106
+ readonly completion_tokens_details?: {
107
+ readonly reasoning_tokens?: number | null;
108
+ } | null;
109
+ readonly prompt_tokens?: number | null;
110
+ readonly completion_tokens?: number | null;
111
+ readonly total_tokens?: number | null;
112
+ readonly prompt_cache_hit_tokens?: number | null;
113
+ } | null;
114
+ readonly choices?: readonly {
61
115
  readonly message?: {
62
- readonly content?: string | null;
63
- /** DeepSeek-native vocabulary (kimi-k2.7-code, deepseek serve this). */
64
- readonly reasoning_content?: string | null;
65
- /** OpenRouter-style vocabulary (kimi-k2.6 via Moonshot serves this). */
66
116
  readonly reasoning?: string | null;
67
- readonly tool_calls?: ReadonlyArray<{
68
- readonly id?: string;
117
+ readonly content?: string | null;
118
+ readonly tool_calls?: readonly {
69
119
  readonly function?: {
70
- readonly name?: string;
71
- readonly arguments?: string;
72
- };
73
- }>;
74
- };
75
- }>;
76
- readonly usage?: CompletionUsage;
77
- }
78
- /** Parse one non-streaming completion into `@effect/ai` encoded parts. */
79
- export declare const fromChatCompletion: (moduleName: string, body: ChatCompletion) => Effect.Effect<ReadonlyArray<unknown>, AiError.AiError>;
120
+ readonly name?: string | null;
121
+ readonly arguments?: string | null;
122
+ } | null;
123
+ readonly id?: (string & import("effect/Brand").Brand<"ToolCallId">) | null;
124
+ }[] | null;
125
+ readonly reasoning_content?: string | null;
126
+ } | null;
127
+ readonly finish_reason?: string | null;
128
+ }[] | null;
129
+ }, reading?: UsageReading | undefined) => Effect.Effect<readonly ResponseParts.PartEncoded[], AiError.AiError, never>;
130
+ /** @public */
80
131
  export declare const makeCompatLanguageModel: (config: CompatConfig) => Effect.Effect<LanguageModel.LanguageModel>;
81
132
  export {};