dsh-loop-engine 0.1.5-rc4 → 0.1.5-rc5

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/lib/index.js CHANGED
@@ -685,6 +685,9 @@ function sessionModelOverrideOf(ctx, session) {
685
685
  }
686
686
 
687
687
  // src/driver-core/model-handover.ts
688
+ var SHIPPED_ROUTE_APIS = {
689
+ "deepseek-official": "openai-completions"
690
+ };
688
691
  var warned = /* @__PURE__ */ new WeakMap();
689
692
  function warnOnce(ctx, key, message) {
690
693
  let seen = warned.get(ctx);
@@ -711,10 +714,9 @@ function readProviderProfile(ctx, address) {
711
714
  const record = node;
712
715
  const { baseURL, api, apiKeyEnv } = record;
713
716
  if (typeof baseURL !== "string" || baseURL.length === 0) return void 0;
714
- if (typeof api !== "string" || api.length === 0) return void 0;
715
717
  return {
716
718
  baseURL,
717
- api,
719
+ api: typeof api === "string" && api.length > 0 ? api : void 0,
718
720
  apiKeyEnv: typeof apiKeyEnv === "string" && apiKeyEnv.length > 0 ? apiKeyEnv : void 0
719
721
  };
720
722
  }
@@ -734,7 +736,7 @@ async function resolveModelHandover(ctx, override) {
734
736
  }
735
737
  const profile = readProviderProfile(ctx, address);
736
738
  if (profile === void 0) {
737
- return refuse(ctx, override, `the "${address.settingsNs}" settings section names no baseURL and wire protocol for it`);
739
+ return refuse(ctx, override, `the "${address.settingsNs}" settings section names no baseURL for it`);
738
740
  }
739
741
  const apiKey = await readApiKey(ctx, profile.apiKeyEnv);
740
742
  if (apiKey === void 0) {
@@ -744,7 +746,7 @@ async function resolveModelHandover(ctx, override) {
744
746
  provider: override.provider,
745
747
  model: override.model,
746
748
  baseURL: profile.baseURL,
747
- api: profile.api,
749
+ api: profile.api ?? SHIPPED_ROUTE_APIS[override.provider],
748
750
  apiKey
749
751
  };
750
752
  }
@@ -2333,15 +2335,15 @@ import { canonicalHeader as canonicalHeader2 } from "@deepseek-ai/dsh-session";
2333
2335
  var CODEX_DSH_PROVIDER = "dsh";
2334
2336
  var CODEX_DSH_API_KEY_ENV = "DSH_LOOP_ENGINE_API_KEY";
2335
2337
  var CODEX_WIRE_APIS = {
2336
- "openai-responses": "responses",
2337
- "openai-completions": "chat"
2338
+ "openai-responses": "responses"
2338
2339
  };
2339
2340
  function tomlString(value) {
2340
2341
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
2341
2342
  }
2342
2343
  function codexModelConfig(handover) {
2343
- const wire = CODEX_WIRE_APIS[handover.api];
2344
+ const wire = handover.api === void 0 ? void 0 : CODEX_WIRE_APIS[handover.api];
2344
2345
  const profile = [
2346
+ `name=${tomlString(CODEX_DSH_PROVIDER)}`,
2345
2347
  `base_url=${tomlString(handover.baseURL)}`,
2346
2348
  ...wire === void 0 ? [] : [`wire_api=${tomlString(wire)}`],
2347
2349
  `env_key=${tomlString(CODEX_DSH_API_KEY_ENV)}`
@@ -4325,10 +4327,11 @@ var PiAgent = class {
4325
4327
  }
4326
4328
  this.assertRequestHeader();
4327
4329
  signal.throwIfAborted();
4328
- const handover = await resolveModelHandover(
4330
+ const resolved = await resolveModelHandover(
4329
4331
  this.loopCtx,
4330
4332
  sessionModelOverrideOf(this.loopCtx, this.session)
4331
4333
  );
4334
+ const handover = resolved?.api === void 0 ? void 0 : resolved;
4332
4335
  const controller = new AbortController();
4333
4336
  const cancel = () => {
4334
4337
  if (!controller.signal.aborted) {
@@ -4685,7 +4688,7 @@ var KIMI_PROVIDER_TYPES = {
4685
4688
  "openai-responses": "openai"
4686
4689
  };
4687
4690
  function kimiModelEnv(handover) {
4688
- const type = KIMI_PROVIDER_TYPES[handover.api];
4691
+ const type = handover.api === void 0 ? void 0 : KIMI_PROVIDER_TYPES[handover.api];
4689
4692
  return {
4690
4693
  KIMI_MODEL_NAME: handover.model,
4691
4694
  KIMI_MODEL_API_KEY: handover.apiKey,
@@ -32,6 +32,11 @@
32
32
  * and never a guess. The warning names the provider and the model, never a
33
33
  * credential.
34
34
  *
35
+ * What counts as "recognized" is deliberately narrow: a non-empty `baseURL` and
36
+ * a resolvable credential. The wire protocol is handed over when the deployment
37
+ * declares one, but its absence does NOT refuse the handover — see
38
+ * {@link resolveModelHandover} and {@link SHIPPED_ROUTE_APIS}.
39
+ *
35
40
  * @module dsh-loop-engine/driver-core/model-handover
36
41
  */
37
42
  import type { Context } from '@deepseek-ai/cordis';
@@ -54,8 +59,13 @@ export interface DshModelHandover {
54
59
  readonly model: string;
55
60
  /** Endpoint base as dsh configured it. */
56
61
  readonly baseURL: string;
57
- /** dsh's wire protocol name for this endpoint. */
58
- readonly api: string;
62
+ /**
63
+ * dsh's wire protocol name for this endpoint, when the deployment declares
64
+ * one (or {@link SHIPPED_ROUTE_APIS} knows it for a shipped route). `undefined`
65
+ * is a normal answer, not a failure: engines that need a protocol degrade the
66
+ * way each one documents, and engines that do not (`claude-code`) ignore it.
67
+ */
68
+ readonly api: string | undefined;
59
69
  /** The resolved credential value; never logged, never evented. */
60
70
  readonly apiKey: string;
61
71
  }
@@ -102,8 +112,16 @@ export interface CredentialsService {
102
112
  * `undefined` is the answer in exactly two situations: the selection names no
103
113
  * real dsh model ({@link SessionModelOverride} is already `undefined` for the
104
114
  * hosted seat), or a real model whose endpoint/credential dsh does not disclose
105
- * to this read. The second case warns once — it is a deployment-shape gap the
106
- * operator can see and fix, not a silent downgrade.
115
+ * to this read — no `baseURL`, or no credential. Both warn once: they are
116
+ * deployment-shape gaps the operator can see and fix, not a silent downgrade.
117
+ *
118
+ * The wire protocol is NOT such a gate. A profile that names no `api` still
119
+ * hands over its endpoint (with `api: undefined`), because refusing would let
120
+ * the engine send dsh's model id to the engine's OWN endpoint — a failure that
121
+ * reads like an auth or model problem while the real one is "wrong endpoint".
122
+ * Shipped routes whose adapter owns the wire are named in
123
+ * {@link SHIPPED_ROUTE_APIS} so they hand over a real protocol; anything still
124
+ * unknown degrades per engine, which is the engine's call to make.
107
125
  *
108
126
  * Called fresh on every step by each driver, so a model or provider picked
109
127
  * mid-conversation reaches the engine's next request rather than being frozen
@@ -3,17 +3,28 @@
3
3
  * overrides and environment that point it at dsh's model.
4
4
  *
5
5
  * Codex resolves a custom endpoint through `model_providers.<id>` in its own
6
- * `config.toml` (`base_url`, `wire_api`, and an `env_key` naming the variable
7
- * the credential is read from), and `codex app-server` accepts those as `-c
8
- * key=value` overrides — so the handover needs no edit of `~/.codex`. The
6
+ * `config.toml` (`name`, `base_url`, `wire_api`, and an `env_key` naming the
7
+ * variable the credential is read from), and `codex app-server` accepts those as
8
+ * `-c key=value` overrides — so the handover needs no edit of `~/.codex`. The
9
9
  * credential travels in the child's environment under {@link CODEX_DSH_API_KEY_ENV},
10
10
  * which the `env_key` names.
11
11
  *
12
- * Codex speaks only OpenAI's wires (`responses` or `chat`). A dsh protocol with
13
- * no codex equivalent (Anthropic Messages) is NOT guessed: `wire_api` is
14
- * omitted, so codex uses its own default wire against dsh's base URL and fails
15
- * loud on the request — the honest outcome, since the plugin cannot make codex
16
- * speak a protocol it does not implement.
12
+ * `name` is not optional in practice even though the CLI's own docs describe it
13
+ * as display metadata: codex 0.149.1 (the version this plugin pins) refuses to
14
+ * load a provider whose `name` is empty — "provider name must not be empty in
15
+ * `model_providers`" — and that config error kills `app-server` on startup, which
16
+ * the driver can only report as "process exited unexpectedly". Omitting it looked
17
+ * correct and failed before any request was ever sent, so every override this
18
+ * module builds carries it (verified against the pinned binary).
19
+ *
20
+ * Codex speaks only OpenAI's `responses` wire as of the version this plugin pins
21
+ * (0.149.1 dropped `chat`). A dsh protocol with no codex equivalent (Anthropic
22
+ * Messages, and now OpenAI Chat Completions) is NOT guessed: `wire_api` is
23
+ * omitted, so codex falls back to its own default wire and fails loud on the
24
+ * request — the honest outcome, since the plugin cannot make codex speak a
25
+ * protocol it does not implement. That default is `responses` as of 0.149.1: the
26
+ * request lands on `<baseURL>/responses`, which an Anthropic Messages or Chat
27
+ * Completions endpoint does not serve.
17
28
  *
18
29
  * @module dsh-loop-engine/engine-codex/model-handover
19
30
  */
@@ -36,7 +47,8 @@ export interface CodexModelConfig {
36
47
  * The provider id ({@link CODEX_DSH_PROVIDER}) is a plugin-owned name: it exists
37
48
  * only in this child's command line, so it cannot collide with a provider the
38
49
  * user configured, and the model itself is still handed over as the thread's own
39
- * `model` param.
50
+ * `model` param. The provider's `name` repeats that id because codex rejects an
51
+ * empty one at config load, before any request is made.
40
52
  * @param handover - the resolved dsh endpoint for the session's selection.
41
53
  * @returns the argv entries and environment to spawn the app-server with.
42
54
  */
@@ -17,6 +17,10 @@
17
17
  * user's own `~/.pi` skills/auth/theme. That is the trade the deployment makes
18
18
  * by selecting a real dsh model on pi.
19
19
  *
20
+ * pi's provider schema requires a wire protocol (`api`), so this is the one
21
+ * engine that cannot express a handover without one; the driver drops such a
22
+ * handover before it reaches here instead of writing a file pi rejects.
23
+ *
20
24
  * @module dsh-loop-engine/engine-pi/model-handover
21
25
  */
22
26
  import type { DshModelHandover } from '../driver-core/model-handover.ts';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-loop-engine",
3
3
  "description": "Web-switchable agent loop engine selection for the DeepSeek Harness - out-of-tree plugin (Claude Code / Codex / Pi / Kimi Code drivers) maintained by @kuun993, zero main-repo changes",
4
- "version": "0.1.5-rc4",
4
+ "version": "0.1.5-rc5",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },