akanjs 3.0.0-beta.12 → 3.0.0-beta.14

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.
@@ -24,16 +24,9 @@ export const agentDictionary = serviceDictionary(["en", "ko"])
24
24
  "The agent is unavailable — this app has no language model configured",
25
25
  "에이전트를 사용할 수 없습니다. 이 앱에 언어 모델이 설정되어 있지 않습니다",
26
26
  ],
27
- deepseekRequestFailed: [
28
- "DeepSeek refused this turn with status {status}. Reason: {reason}",
29
- "DeepSeek가 이번 턴을 거절했습니다 (status {status}). 사유: {reason}",
30
- ],
31
- openaiRequestFailed: [
32
- "OpenAI refused this turn with status {status}. Reason: {reason}",
33
- "OpenAI가 이번 턴을 거절했습니다 (status {status}). 사유: {reason}",
34
- ],
35
- anthropicRequestFailed: [
36
- "Anthropic refused this turn with status {status}. Reason: {reason}",
37
- "Anthropic이 이번 턴을 거절했습니다 (status {status}). 사유: {reason}",
27
+
28
+ llmRequestFailed: [
29
+ "{provider} refused this turn with status {status}. Reason: {reason}",
30
+ "{provider}가 이번 턴을 거절했습니다 (status {status}). 사유: {reason}",
38
31
  ],
39
32
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-beta.12",
3
+ "version": "3.0.0-beta.14",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -73,8 +73,15 @@ export class AkanOption<Env extends BackendEnv = BackendEnv> {
73
73
  this.#crossSite = crossSite;
74
74
  return this;
75
75
  }
76
- /** Settings for whichever adaptor fills `LlmAdaptorRole`, injected into it as the `llmOption` use. */
77
- setLlm(llmOrFn: LlmOption | ((env: Env) => LlmOption)) {
76
+ /**
77
+ * Settings for whichever adaptor fills `LlmAdaptorRole`, injected into it as the `llmOption` use.
78
+ *
79
+ * The argument is generic so that whatever an adaptor needs beyond `LlmOption` travels here too: an adaptor an
80
+ * app or a library wrote declares its own interface extending it, reads it with `use<MyLlmOption>()`, and its
81
+ * region or project id rides the same channel the shipped fields do. Entries merge in mount order with the
82
+ * app's last, so a library may name a host and the app the key.
83
+ */
84
+ setLlm<Option extends LlmOption>(llmOrFn: Option | ((env: Env) => Option)) {
78
85
  if (typeof llmOrFn === "function") this.#getLlms.push(llmOrFn);
79
86
  else this.#getLlms.push(() => llmOrFn);
80
87
  return this;
@@ -10,13 +10,13 @@ import {
10
10
  ConsoleLogger,
11
11
  type DatabaseAdaptor,
12
12
  DatabaseAdaptorRole,
13
- DeepseekLlm,
14
13
  JsonCompressor,
15
14
  LibsqlDatabase,
16
15
  type LlmAdaptor,
17
16
  LlmAdaptorRole,
18
17
  type LoggingAdaptor,
19
18
  LoggingAdaptorRole,
19
+ OpenaiLlm,
20
20
  PostgresDatabase,
21
21
  type QueueAdaptor,
22
22
  QueueAdaptorRole,
@@ -68,7 +68,7 @@ export const predefinedAdaptor = {
68
68
  logging: ConsoleLogger,
69
69
  websocket: SolidPubSub,
70
70
  compress: JsonCompressor,
71
- llm: DeepseekLlm,
71
+ llm: OpenaiLlm,
72
72
  };
73
73
 
74
74
  export const getPredefinedAdaptor = (mode: DatabaseMode = "single"): PredefinedAdaptor => {
@@ -9,6 +9,7 @@ import type {
9
9
  LlmTurnAnswer,
10
10
  LlmTurnRequest,
11
11
  } from "./llm.adaptor";
12
+ import { llmProviderOf } from "./llm.adaptor";
12
13
 
13
14
  type AnthropicSource = { type: "base64"; media_type: string; data: string } | { type: "url"; url: string };
14
15
  type AnthropicBlock =
@@ -136,7 +137,7 @@ export class AnthropicLlm
136
137
 
137
138
  signal: AbortSignal.timeout(120_000),
138
139
  });
139
- if (!response.ok) throw await AnthropicLlm.refusal(response);
140
+ if (!response.ok) throw await AnthropicLlm.refusal(this.#host, response);
140
141
  return (await response.json()) as T;
141
142
  }
142
143
 
@@ -147,12 +148,13 @@ export class AnthropicLlm
147
148
  body: JSON.stringify(body),
148
149
  signal: AbortSignal.timeout(120_000),
149
150
  });
150
- if (!response.ok || !response.body) throw await AnthropicLlm.refusal(response);
151
+ if (!response.ok || !response.body) throw await AnthropicLlm.refusal(this.#host, response);
151
152
  return response.body;
152
153
  }
153
154
 
154
- static async refusal(response: Response): Promise<Error> {
155
- return new Err("agent.error.anthropicRequestFailed", {
155
+ static async refusal(host: string, response: Response): Promise<Error> {
156
+ return new Err("agent.error.llmRequestFailed", {
157
+ provider: llmProviderOf(host),
156
158
  status: String(response.status),
157
159
  reason: await AnthropicLlm.reasonOf(response),
158
160
  });
@@ -2,7 +2,6 @@ export * from "./anthropicLlm";
2
2
  export * from "./cache.adaptor";
3
3
  export * from "./compress.adaptor";
4
4
  export * from "./database.adaptor";
5
- export * from "./deepseekLlm";
6
5
  export * from "./insightQuery";
7
6
  export * from "./llm.adaptor";
8
7
  export * from "./logging.adaptor";
@@ -131,7 +131,12 @@ export interface LlmAccepts {
131
131
  /**
132
132
  * Settings for whichever adaptor fills `LlmAdaptorRole`, registered with `option.setLlm(...)` and injected as the
133
133
  * `llmOption` use. It belongs to the role rather than to one provider: swapping the default for another `adapt()`
134
- * class re-reads the same three fields under that provider's own defaults.
134
+ * class re-reads the same fields under that provider's own defaults.
135
+ *
136
+ * It is the floor, not the whole shape. `setLlm` keeps whatever else it is handed, so an adaptor an app or a
137
+ * library wrote declares its own interface extending this one and reads it with `use<MyLlmOption>()` — a region,
138
+ * a project id, a deployment name reach it through the same channel the fields below do, instead of a second
139
+ * `option.use({...})` key beside it.
135
140
  */
136
141
  export interface LlmOption {
137
142
  apiKey?: string;
@@ -156,3 +161,18 @@ export interface LlmOption {
156
161
  */
157
162
  maxTokens?: number;
158
163
  }
164
+
165
+ /**
166
+ * What the chat prints as the party that refused a turn, carried on `agent.error.llmRequestFailed`.
167
+ *
168
+ * It is the host rather than the adaptor's own name because one adaptor speaks one dialect to whatever host it
169
+ * is pointed at — an OpenAI-dialect class aimed at a gateway would otherwise credit OpenAI for that gateway's
170
+ * refusal. A host that is not a URL is printed as written; there is nothing better to say about it.
171
+ */
172
+ export const llmProviderOf = (host: string): string => {
173
+ try {
174
+ return new URL(host).hostname;
175
+ } catch {
176
+ return host;
177
+ }
178
+ };
@@ -1,18 +1,27 @@
1
1
  import { Err } from "akanjs/dictionary";
2
2
  import { adapt } from "../adapt";
3
- import type { LlmAccepts, LlmAdaptor, LlmOption, LlmTurnAnswer, LlmTurnRequest } from "./llm.adaptor";
3
+ import {
4
+ type LlmAccepts,
5
+ type LlmAdaptor,
6
+ type LlmOption,
7
+ type LlmTurnAnswer,
8
+ type LlmTurnRequest,
9
+ llmProviderOf,
10
+ } from "./llm.adaptor";
4
11
  import { type OpenaiAnswer, OpenaiDialect } from "./openaiDialect";
5
12
 
6
13
  /**
7
- * OpenAI's chat-completions endpoint, and every gateway that serves the same dialect `host` is what points it
8
- * at one. It is `DeepseekLlm`'s sibling rather than its replacement: same wire, and the difference that earns a
9
- * second class is that this one declares `accepts`, so an attached image reaches the model as an image part
10
- * instead of a note saying it could not be read.
14
+ * The OpenAI chat-completions dialect, pointed at a host and the framework's default fill for `LlmAdaptorRole`.
15
+ *
16
+ * One class rather than one per vendor: DeepSeek, Groq, Together, OpenRouter, Ollama and a self-hosted vLLM all
17
+ * serve this same wire, so what distinguishes them is `option.setLlm({ host, model })` and not a protocol. A
18
+ * provider that speaks its own wire — Anthropic's blocks, Bedrock's signed requests — is a different adaptor
19
+ * class, in this package or in the app's own `srvkit/`, applied with
20
+ * `option.applyAdaptor(LlmAdaptorRole, TheClass)`.
11
21
  *
12
22
  * `model` is required and has no default. A default would be a model name that ages out of the provider's
13
- * catalogue into a 404 at the first turn, and — worse here than for a text-only adaptor it would decide the
14
- * vision claim below on the app's behalf. Name the model in `option.setLlm({ model })`, and name
15
- * `accepts: { image: false }` beside it when that model is one of the provider's text-only ones.
23
+ * catalogue into a 404 at the first turn, and — worse it would decide the vision claim below on the app's
24
+ * behalf.
16
25
  */
17
26
  export class OpenaiLlm
18
27
  extends adapt("openaiLlm" as const, ({ use }) => ({
@@ -20,13 +29,22 @@ export class OpenaiLlm
20
29
  }))
21
30
  implements LlmAdaptor
22
31
  {
32
+ static readonly defaultHost = "https://api.openai.com/v1";
33
+
23
34
  get #host() {
24
- return this.llmOption.host ?? "https://api.openai.com/v1";
35
+ return this.llmOption.host ?? OpenaiLlm.defaultHost;
25
36
  }
26
37
 
27
- /** The endpoint takes image parts, so that is the provider's answer; a model that does not takes the override. */
28
- get accepts(): LlmAccepts {
29
- return this.llmOption.accepts ?? { image: true };
38
+ /**
39
+ * OpenAI's own endpoint takes image parts, so that is what is claimed for the default host. A host the app
40
+ * named is a gateway this class knows nothing about, and claiming vision for one is the worst guess available:
41
+ * the bytes reach a model that cannot decode them and the whole turn dies on a 400, where text-only degrades
42
+ * them to a note the model can repeat back. So a named host is text-only until `option.setLlm({ accepts })`
43
+ * says otherwise — as is the OpenAI model that reads no image.
44
+ */
45
+ get accepts(): LlmAccepts | undefined {
46
+ if (this.llmOption.accepts) return this.llmOption.accepts;
47
+ return this.llmOption.host ? undefined : { image: true };
30
48
  }
31
49
 
32
50
  async chat(request: LlmTurnRequest, onDelta?: (delta: string) => void): Promise<LlmTurnAnswer | null> {
@@ -52,8 +70,8 @@ export class OpenaiLlm
52
70
  );
53
71
  return await OpenaiDialect.consumeStream(body, onDelta);
54
72
  } catch (error) {
55
-
56
- this.logger.error(`OpenAI turn failed: ${error instanceof Error ? error.message : String(error)}`);
73
+
74
+ this.logger.error(`LLM turn failed: ${error instanceof Error ? error.message : String(error)}`);
57
75
  throw error;
58
76
  }
59
77
  }
@@ -66,7 +84,7 @@ export class OpenaiLlm
66
84
 
67
85
  signal: AbortSignal.timeout(120_000),
68
86
  });
69
- if (!response.ok) throw await OpenaiLlm.refusal(response);
87
+ if (!response.ok) throw await OpenaiLlm.refusal(this.#host, response);
70
88
  return (await response.json()) as T;
71
89
  }
72
90
 
@@ -77,13 +95,14 @@ export class OpenaiLlm
77
95
  body: JSON.stringify(body),
78
96
  signal: AbortSignal.timeout(120_000),
79
97
  });
80
- if (!response.ok || !response.body) throw await OpenaiLlm.refusal(response);
98
+ if (!response.ok || !response.body) throw await OpenaiLlm.refusal(this.#host, response);
81
99
  return response.body;
82
100
  }
83
101
 
84
102
  /** Carried on the `Err` so the chat prints the provider's own sentence rather than a status number. */
85
- static async refusal(response: Response): Promise<Error> {
86
- return new Err("agent.error.openaiRequestFailed", {
103
+ static async refusal(host: string, response: Response): Promise<Error> {
104
+ return new Err("agent.error.llmRequestFailed", {
105
+ provider: llmProviderOf(host),
87
106
  status: String(response.status),
88
107
  reason: await OpenaiDialect.reasonOf(response),
89
108
  });
@@ -1 +1 @@
1
- export declare const agentDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "runAgentTurn", "llmUnavailable" | "deepseekRequestFailed" | "openaiRequestFailed" | "anthropicRequestFailed", never>;
1
+ export declare const agentDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "runAgentTurn", "llmUnavailable" | "llmRequestFailed", never>;
@@ -2,12 +2,12 @@ import type { AgentEndpoint, AgentTurn, BaseEndpoint } from "akanjs/signal";
2
2
  export declare const dictionary: {
3
3
  base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view">, "base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse">;
4
4
  agentTurn: import("./locale.d.ts").DictModule<import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc`, never>;
5
- agent: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed" | "agent.error.openaiRequestFailed" | "agent.error.anthropicRequestFailed">;
5
+ agent: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, "agent.error.llmUnavailable" | "agent.error.llmRequestFailed">;
6
6
  };
7
- export declare const Err: import("./trans.d.ts").ErrConstructor<"base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse" | "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed" | "agent.error.openaiRequestFailed" | "agent.error.anthropicRequestFailed">, translate: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
7
+ export declare const Err: import("./trans.d.ts").ErrConstructor<"base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse" | "agent.error.llmUnavailable" | "agent.error.llmRequestFailed">, translate: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
8
8
  info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
9
9
  success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
10
10
  error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
11
11
  warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
12
12
  loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
13
- }, getDictionary: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse" | "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed" | "agent.error.openaiRequestFailed" | "agent.error.anthropicRequestFailed";
13
+ }, getDictionary: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "unauthorized" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "success" | "somethingWrong" | "connecting" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachReading" | "agentReferenceRemove" | "agentReferenceFailed" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "new" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse" | "agent.error.llmUnavailable" | "agent.error.llmRequestFailed";
@@ -38,8 +38,15 @@ export declare class AkanOption<Env extends BackendEnv = BackendEnv> {
38
38
  * itself is on by default and `{ enabled: false }` is for an API no browser reaches.
39
39
  */
40
40
  setCrossSite(crossSite: CrossSiteOption): this;
41
- /** Settings for whichever adaptor fills `LlmAdaptorRole`, injected into it as the `llmOption` use. */
42
- setLlm(llmOrFn: LlmOption | ((env: Env) => LlmOption)): this;
41
+ /**
42
+ * Settings for whichever adaptor fills `LlmAdaptorRole`, injected into it as the `llmOption` use.
43
+ *
44
+ * The argument is generic so that whatever an adaptor needs beyond `LlmOption` travels here too: an adaptor an
45
+ * app or a library wrote declares its own interface extending it, reads it with `use<MyLlmOption>()`, and its
46
+ * region or project id rides the same channel the shipped fields do. Entries merge in mount order with the
47
+ * app's last, so a library may name a host and the app the key.
48
+ */
49
+ setLlm<Option extends LlmOption>(llmOrFn: Option | ((env: Env) => Option)): this;
43
50
  /** Every entry in declaration order, duplicates kept: the boot stage rejects a key claimed twice. */
44
51
  getUses(env: Env): [string, PromiseOrObject<unknown>][];
45
52
  getMiddlewares(): MiddlewareCls[];
@@ -1,5 +1,5 @@
1
1
  import type { DatabaseMode } from "akanjs";
2
- import { type AdaptorCls, BlobStorage, type CacheAdaptor, type CompressAdaptor, ConsoleLogger, type DatabaseAdaptor, DeepseekLlm, JsonCompressor, type LlmAdaptor, type LoggingAdaptor, type QueueAdaptor, type ScheduleAdaptor, Scheduler, SolidCache, SolidPubSub, SolidQueue, SqliteDatabase, type StorageAdaptor, type WebsocketAdaptor } from "akanjs/service";
2
+ import { type AdaptorCls, BlobStorage, type CacheAdaptor, type CompressAdaptor, ConsoleLogger, type DatabaseAdaptor, JsonCompressor, type LlmAdaptor, type LoggingAdaptor, OpenaiLlm, type QueueAdaptor, type ScheduleAdaptor, Scheduler, SolidCache, SolidPubSub, SolidQueue, SqliteDatabase, type StorageAdaptor, type WebsocketAdaptor } from "akanjs/service";
3
3
  export interface PredefinedAdaptor {
4
4
  database: AdaptorCls<DatabaseAdaptor>;
5
5
  cache: AdaptorCls<CacheAdaptor>;
@@ -31,6 +31,6 @@ export declare const predefinedAdaptor: {
31
31
  logging: typeof ConsoleLogger;
32
32
  websocket: typeof SolidPubSub;
33
33
  compress: typeof JsonCompressor;
34
- llm: typeof DeepseekLlm;
34
+ llm: typeof OpenaiLlm;
35
35
  };
36
36
  export declare const getPredefinedAdaptor: (mode?: DatabaseMode) => PredefinedAdaptor;
@@ -78,7 +78,7 @@ export declare class AnthropicLlm extends AnthropicLlm_base implements LlmAdapto
78
78
  /** What the API's blocks carry. A model of the family that reads neither takes the `accepts` override. */
79
79
  get accepts(): LlmAccepts;
80
80
  chat(request: LlmTurnRequest, onDelta?: (delta: string) => void): Promise<LlmTurnAnswer | null>;
81
- static refusal(response: Response): Promise<Error>;
81
+ static refusal(host: string, response: Response): Promise<Error>;
82
82
  /** The API answers a refusal as `{ error: { type, message } }`, and the sentence is the half worth printing. */
83
83
  static reasonOf(response: Response): Promise<string>;
84
84
  static requestBody(model: string, request: LlmTurnRequest, { accepts, stream, maxTokens }?: {
@@ -2,7 +2,6 @@ export * from "./anthropicLlm.d.ts";
2
2
  export * from "./cache.adaptor";
3
3
  export * from "./compress.adaptor";
4
4
  export * from "./database.adaptor";
5
- export * from "./deepseekLlm.d.ts";
6
5
  export * from "./insightQuery.d.ts";
7
6
  export * from "./llm.adaptor";
8
7
  export * from "./logging.adaptor";
@@ -120,7 +120,12 @@ export interface LlmAccepts {
120
120
  /**
121
121
  * Settings for whichever adaptor fills `LlmAdaptorRole`, registered with `option.setLlm(...)` and injected as the
122
122
  * `llmOption` use. It belongs to the role rather than to one provider: swapping the default for another `adapt()`
123
- * class re-reads the same three fields under that provider's own defaults.
123
+ * class re-reads the same fields under that provider's own defaults.
124
+ *
125
+ * It is the floor, not the whole shape. `setLlm` keeps whatever else it is handed, so an adaptor an app or a
126
+ * library wrote declares its own interface extending this one and reads it with `use<MyLlmOption>()` — a region,
127
+ * a project id, a deployment name reach it through the same channel the fields below do, instead of a second
128
+ * `option.use({...})` key beside it.
124
129
  */
125
130
  export interface LlmOption {
126
131
  apiKey?: string;
@@ -145,3 +150,11 @@ export interface LlmOption {
145
150
  */
146
151
  maxTokens?: number;
147
152
  }
153
+ /**
154
+ * What the chat prints as the party that refused a turn, carried on `agent.error.llmRequestFailed`.
155
+ *
156
+ * It is the host rather than the adaptor's own name because one adaptor speaks one dialect to whatever host it
157
+ * is pointed at — an OpenAI-dialect class aimed at a gateway would otherwise credit OpenAI for that gateway's
158
+ * refusal. A host that is not a URL is printed as written; there is nothing better to say about it.
159
+ */
160
+ export declare const llmProviderOf: (host: string) => string;
@@ -1,24 +1,33 @@
1
- import type { LlmAccepts, LlmAdaptor, LlmOption, LlmTurnAnswer, LlmTurnRequest } from "./llm.adaptor";
1
+ import { type LlmAccepts, type LlmAdaptor, type LlmOption, type LlmTurnAnswer, type LlmTurnRequest } from "./llm.adaptor";
2
2
  declare const OpenaiLlm_base: import("..").AdaptorCls<{}, {
3
3
  llmOption: import("..").InjectInfo<"use", LlmOption, never, never>;
4
4
  }>;
5
5
  /**
6
- * OpenAI's chat-completions endpoint, and every gateway that serves the same dialect `host` is what points it
7
- * at one. It is `DeepseekLlm`'s sibling rather than its replacement: same wire, and the difference that earns a
8
- * second class is that this one declares `accepts`, so an attached image reaches the model as an image part
9
- * instead of a note saying it could not be read.
6
+ * The OpenAI chat-completions dialect, pointed at a host and the framework's default fill for `LlmAdaptorRole`.
7
+ *
8
+ * One class rather than one per vendor: DeepSeek, Groq, Together, OpenRouter, Ollama and a self-hosted vLLM all
9
+ * serve this same wire, so what distinguishes them is `option.setLlm({ host, model })` and not a protocol. A
10
+ * provider that speaks its own wire — Anthropic's blocks, Bedrock's signed requests — is a different adaptor
11
+ * class, in this package or in the app's own `srvkit/`, applied with
12
+ * `option.applyAdaptor(LlmAdaptorRole, TheClass)`.
10
13
  *
11
14
  * `model` is required and has no default. A default would be a model name that ages out of the provider's
12
- * catalogue into a 404 at the first turn, and — worse here than for a text-only adaptor it would decide the
13
- * vision claim below on the app's behalf. Name the model in `option.setLlm({ model })`, and name
14
- * `accepts: { image: false }` beside it when that model is one of the provider's text-only ones.
15
+ * catalogue into a 404 at the first turn, and — worse it would decide the vision claim below on the app's
16
+ * behalf.
15
17
  */
16
18
  export declare class OpenaiLlm extends OpenaiLlm_base implements LlmAdaptor {
17
19
  #private;
18
- /** The endpoint takes image parts, so that is the provider's answer; a model that does not takes the override. */
19
- get accepts(): LlmAccepts;
20
+ static readonly defaultHost = "https://api.openai.com/v1";
21
+ /**
22
+ * OpenAI's own endpoint takes image parts, so that is what is claimed for the default host. A host the app
23
+ * named is a gateway this class knows nothing about, and claiming vision for one is the worst guess available:
24
+ * the bytes reach a model that cannot decode them and the whole turn dies on a 400, where text-only degrades
25
+ * them to a note the model can repeat back. So a named host is text-only until `option.setLlm({ accepts })`
26
+ * says otherwise — as is the OpenAI model that reads no image.
27
+ */
28
+ get accepts(): LlmAccepts | undefined;
20
29
  chat(request: LlmTurnRequest, onDelta?: (delta: string) => void): Promise<LlmTurnAnswer | null>;
21
30
  /** Carried on the `Err` so the chat prints the provider's own sentence rather than a status number. */
22
- static refusal(response: Response): Promise<Error>;
31
+ static refusal(host: string, response: Response): Promise<Error>;
23
32
  }
24
33
  export {};
@@ -1,82 +0,0 @@
1
- import { Err } from "akanjs/dictionary";
2
- import { adapt } from "../adapt";
3
- import type { LlmAdaptor, LlmOption, LlmTurnAnswer, LlmTurnRequest } from "./llm.adaptor";
4
- import { type OpenaiAnswer, OpenaiDialect } from "./openaiDialect";
5
-
6
- /**
7
- * The framework's default provider, and the one an app gets without choosing.
8
- *
9
- * `accepts` is left undeclared, so by the time an attachment reaches the dialect `AgentService.readable` has
10
- * reduced it to its text and turned everything else into a note. That is deliberate rather than pending: DeepSeek's
11
- * chat API is text, and an adaptor that claimed otherwise would hand it bytes it answers about having never seen.
12
- * An app that wants vision swaps the role — `option.applyAdaptor(LlmAdaptorRole, OpenaiLlm)` or `AnthropicLlm`.
13
- */
14
- export class DeepseekLlm
15
- extends adapt("deepseekLlm" as const, ({ use }) => ({
16
- llmOption: use<LlmOption>(),
17
- }))
18
- implements LlmAdaptor
19
- {
20
- get #model() {
21
- return this.llmOption.model ?? "deepseek-v4-flash";
22
- }
23
- get #host() {
24
- return this.llmOption.host ?? "https://api.deepseek.com";
25
- }
26
-
27
- async chat(request: LlmTurnRequest, onDelta?: (delta: string) => void): Promise<LlmTurnAnswer | null> {
28
- if (!this.llmOption.apiKey) {
29
- this.logger.warn("No LLM API key is configured — set one with option.setLlm(). Agent turns are unavailable.");
30
- return null;
31
- }
32
- try {
33
- if (!onDelta) {
34
- const answer = await this.#api<OpenaiAnswer>(
35
- "/chat/completions",
36
- OpenaiDialect.requestBody(this.#model, request),
37
- );
38
- return OpenaiDialect.turnAnswer(answer);
39
- }
40
- const body = await this.#apiStream(
41
- "/chat/completions",
42
- OpenaiDialect.requestBody(this.#model, request, { stream: true }),
43
- );
44
- return await OpenaiDialect.consumeStream(body, onDelta);
45
- } catch (error) {
46
-
47
- this.logger.error(`DeepSeek turn failed: ${error instanceof Error ? error.message : String(error)}`);
48
- throw error;
49
- }
50
- }
51
-
52
- async #api<T>(path: string, body: object): Promise<T> {
53
- const response = await fetch(`${this.#host}${path}`, {
54
- method: "POST",
55
- headers: { "content-type": "application/json", authorization: `Bearer ${this.llmOption.apiKey}` },
56
- body: JSON.stringify(body),
57
-
58
- signal: AbortSignal.timeout(120_000),
59
- });
60
- if (!response.ok) throw await DeepseekLlm.refusal(response);
61
- return (await response.json()) as T;
62
- }
63
-
64
- async #apiStream(path: string, body: object): Promise<ReadableStream<Uint8Array>> {
65
- const response = await fetch(`${this.#host}${path}`, {
66
- method: "POST",
67
- headers: { "content-type": "application/json", authorization: `Bearer ${this.llmOption.apiKey}` },
68
- body: JSON.stringify(body),
69
- signal: AbortSignal.timeout(120_000),
70
- });
71
- if (!response.ok || !response.body) throw await DeepseekLlm.refusal(response);
72
- return response.body;
73
- }
74
-
75
- /** Carried on the `Err` so the chat prints the provider's own sentence rather than a status number. */
76
- static async refusal(response: Response): Promise<Error> {
77
- return new Err("agent.error.deepseekRequestFailed", {
78
- status: String(response.status),
79
- reason: await OpenaiDialect.reasonOf(response),
80
- });
81
- }
82
- }
@@ -1,19 +0,0 @@
1
- import type { LlmAdaptor, LlmOption, LlmTurnAnswer, LlmTurnRequest } from "./llm.adaptor";
2
- declare const DeepseekLlm_base: import("..").AdaptorCls<{}, {
3
- llmOption: import("..").InjectInfo<"use", LlmOption, never, never>;
4
- }>;
5
- /**
6
- * The framework's default provider, and the one an app gets without choosing.
7
- *
8
- * `accepts` is left undeclared, so by the time an attachment reaches the dialect `AgentService.readable` has
9
- * reduced it to its text and turned everything else into a note. That is deliberate rather than pending: DeepSeek's
10
- * chat API is text, and an adaptor that claimed otherwise would hand it bytes it answers about having never seen.
11
- * An app that wants vision swaps the role — `option.applyAdaptor(LlmAdaptorRole, OpenaiLlm)` or `AnthropicLlm`.
12
- */
13
- export declare class DeepseekLlm extends DeepseekLlm_base implements LlmAdaptor {
14
- #private;
15
- chat(request: LlmTurnRequest, onDelta?: (delta: string) => void): Promise<LlmTurnAnswer | null>;
16
- /** Carried on the `Err` so the chat prints the provider's own sentence rather than a status number. */
17
- static refusal(response: Response): Promise<Error>;
18
- }
19
- export {};