dsh-connect-agnes-token-plan 0.5.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.
@@ -0,0 +1,147 @@
1
+ # dsh-connect-agnes-token-plan — the bundle's own patch layer.
2
+ #
3
+ # Reads the Agnes Token Plan quota through the console API and shows it in
4
+ # a config card on the Plugins page (`plugins.bundle.config` slot) — not in the
5
+ # sidebar. Everything below is plain configuration: edit it here (or
6
+ # override the same id in the profile's cordis.patch.yml) and reinstall/reload.
7
+ #
8
+ # The console token is NOT configured here. Open the panel and sign in once:
9
+ # the Host logs in, keeps the account in the DSH credentials, and re-logs in
10
+ # on its own when the token dies (Agnes issues no refresh token). See README.md.
11
+ - insert:
12
+ - id: dsh-connect-agnes-token-plan
13
+ name: dsh-connect-agnes-token-plan
14
+ config:
15
+ # Console origin (the BACKEND host, not the console front-end: the
16
+ # front-end origin serves /api/* as a Next.js 404 shell). Override if
17
+ # you use a mirror or proxy.
18
+ consoleBase: https://platform-backend.agnes-ai.cn
19
+ # The OpenAI-compatible API base, used only for the optional model
20
+ # catalog (which models this key can call). Leave it alone unless the
21
+ # platform moves the endpoint.
22
+ apiBase: https://api.agnes-ai.cn/v1
23
+ # How many days of usage the chart covers (max 365). Days, not hours:
24
+ # the console's series endpoint takes start_date / end_date as DATES.
25
+ usageDays: 30
26
+ # Pseudo multipliers for the model roster: keys are case-insensitive
27
+ # SUBSTRINGS of a model id (first match in writing order wins), values
28
+ # are positive numbers. Empty by default — the factor used to scale a
29
+ # per-model credit figure, and Agnes publishes no per-model usage at
30
+ # all. What is left is your own annotation: a `×N` badge on a roster
31
+ # row. Rows without a match get no badge, never a guessed 1.
32
+ # Invalid entries (empty key / value <= 0 / non-number) are dropped.
33
+ trendMultipliers: {}
34
+ # How long the Host caches each console response, in seconds.
35
+ cacheSeconds: 60
36
+ # How often the panel asks the Host again, in seconds. The Host states
37
+ # this to the bundle on every snapshot, so the two cannot disagree about
38
+ # how fresh the screen is.
39
+ pollSeconds: 30
40
+ # Deadline for one console call, in milliseconds. The login flow has its
41
+ # own (loginTimeoutMs, below) because it carries a password and may be
42
+ # rate-limited, so the two are not the same number.
43
+ consoleTimeoutMs: 15000
44
+ # Renew the console token this many seconds before it actually expires,
45
+ # so a poll never races the expiry boundary. 120 = two minutes.
46
+ tokenSkewSeconds: 120
47
+ # Extra host names this Host answers as, beyond the loopback defaults
48
+ # (localhost / 127.0.0.1 / ::1). ADDED to them, never substituted — see
49
+ # docs/SETUP.md. Uncomment and list to serve the panel from another name.
50
+ # allowedHosts: []
51
+ # The login flow's overrides. Everything below is optional: a key left
52
+ # commented keeps the platform default. Agnes signs in with ONE request
53
+ # (POST {consoleBase}/api/user/login), so there is no OIDC walk and none
54
+ # of the SenseNova endpoint knobs exist here any more.
55
+ # loginPath: /api/user/login
56
+ # -> the sign-in path, if the platform ever moves it.
57
+ # fallbackExpiresInSeconds: 604800
58
+ # -> lifetime assumed when the access token is not a readable JWT
59
+ # (7 days by default). Deliberately LONG: a token assumed dead
60
+ # while still alive spends a real sign-in on every poll, and Agnes
61
+ # locks an account after a few bad attempts.
62
+ # loginTimeoutMs: 20000
63
+ # -> deadline for one sign-in attempt, in ms. requestTimeoutMs is its
64
+ # older spelling and still wins when only it is set; the console
65
+ # calls use consoleTimeoutMs above.
66
+ # requestTimeoutMs: 20000
67
+ # ------------------------------------------------------------------
68
+ # Vision step two: publish which of this key's models accept image
69
+ # input into this row's OWN settings namespace, for a later LLM
70
+ # connect plugin to read. It writes only its own row - never another
71
+ # provider's imageModelIds - so a miscalculated model list can only
72
+ # affect the panel, not DSH's model routing.
73
+ #
74
+ # `writeImageModelIds: true` -> the Host syncs visionModels into
75
+ # `imageModelIds` on this row's settings on every catalog poll
76
+ # (opt-in, off by default; the panel's read-only info layer is the
77
+ # safe shape).
78
+ # `imageModelIds: []` -> the last written model-id list.
79
+ # `visionModels: []` -> the last full vision identification
80
+ # (ids + source markers), kept for a reader that wants the detail.
81
+ writeImageModelIds: false
82
+ imageModelIds: []
83
+ visionModels: []
84
+ # ------------------------------------------------------------------
85
+ # Step three ("one-stop service"): register the Agnes LLM
86
+ # provider DIRECTLY, with no hand-written llm-pi-ai row. When true
87
+ # the Host offers an OpenAI-compatible provider
88
+ # (`agnes-token-plan`, base apiBase above): the panel's API-key
89
+ # field stores the `sk-` key in the DSH credentials (the process
90
+ # environment still works as a fallback), the catalog poll builds
91
+ # the model list, and vision models accept image input
92
+ # automatically. The cached catalog and the model allow-list live
93
+ # in this plugin's own state file, never on this row. Off by
94
+ # default - registering a model source is a Host-wide change.
95
+ # This value is now the DEPLOYMENT DEFAULT only: the panel's
96
+ #「模型接入」switch (POST /api/<name>/provider) overrides it live,
97
+ # with no restart. See docs/PROVIDER-HOT-RELOAD.md.
98
+ registerProvider: false
99
+ # ------------------------------------------------------------------
100
+ # Draw absorption (docs/ARCHITECTURE.md §5.4, route B): when true AND
101
+ # the Host exposes a tools service, the agent gains an
102
+ # `agnes_draw_image` tool that POSTs to
103
+ # `{apiBase}/images/generations` with the panel-saved
104
+ # `AGNES_TOKEN_PLAN_API_KEY`. Draw models are identified by the
105
+ # catalog's own `output_modalities` when the platform declares it and
106
+ # by an `image` segment in the model id when it does not - the Agnes
107
+ # gateway declares NO modality field at all (docs/AGNES-API.md §7.1),
108
+ # so the name fallback is the path that actually fires here. The key is
109
+ # resolved per call, and a failed draw starts a 30s cooldown. Off by
110
+ # default - an agent tool is a Host-wide change; a Host without the
111
+ # tools service simply never sees it.
112
+ # drawEnabled: false
113
+ # -> the opt-in switch (strict boolean).
114
+ # drawModelId: ""
115
+ # -> preferred image model id; empty = first of the catalog
116
+ # (e.g. Agnes-u1-fast).
117
+ # drawTimeoutMs: 120000
118
+ # -> deadline for one image request (image models are slow).
119
+ # ------------------------------------------------------------------
120
+ # ------------------------------------------------------------------
121
+ # Video absorption (docs/AGNES-API.md §7.5): the same ladder as draw,
122
+ # with one protocol difference that decides the shape - video is an
123
+ # ASYNCHRONOUS TASK, so the tool creates a task and polls it. Off by
124
+ # default, like the draw tool.
125
+ # videoEnabled: false
126
+ # -> the opt-in switch (strict boolean).
127
+ # videoModelId: ""
128
+ # -> preferred video model id; empty = first V2.0-family model in the
129
+ # catalog. The 2.5 family (`agnes-video-2.5`, `-2.5-flash`) is
130
+ # NOT selectable: it takes `mode`/`seconds`/`size`/`aspect_ratio`
131
+ # instead of `width`/`height`/`num_frames`/`frame_rate`, and
132
+ # mixing the two schemas is a 400. The panel names the excluded
133
+ # models so their absence is explained rather than mysterious.
134
+ # videoTimeoutMs: 600000
135
+ # -> deadline for the WHOLE task (create + poll). Minutes, not
136
+ # seconds; floor 30000.
137
+ # videoWidth: 1152
138
+ # videoHeight: 768
139
+ # -> the V2.0 frame size. Passed straight through.
140
+ # videoNumFrames: 121
141
+ # -> frame count; must be <= 441 AND satisfy 8n+1 (81 / 121 / 161 /
142
+ # 241 / 441). 121 @ 24fps is about 5 seconds. An illegal explicit
143
+ # value is REFUSED, not clamped: silently shortening a clip the
144
+ # caller asked for is worse than an error they can read.
145
+ # videoFrameRate: 24
146
+ # -> frames per second, 1-60.
147
+ # ------------------------------------------------------------------
@@ -0,0 +1,135 @@
1
+ import { a as name } from "./host-config-BNGgm_j7.js";
2
+ import { i as buildAgnescodeDescriptors, n as AGNESCODE_PROVIDER_ID, r as agnescodeRoster, t as AGNESCODE_DISPLAY_NAME } from "./agnescode-models-CUcL9UwK.js";
3
+ import { n as buildRetryPolicyConfig, t as reclassifyStream } from "./llm-error-fix-DQUng8AL.js";
4
+ import { createProvider } from "@earendil-works/pi-ai";
5
+ import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
6
+ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
7
+ import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
8
+
9
+ //#region src/host/agnescode-llm-adapter.ts
10
+ /**
11
+ * The peer-dependent half of the directly-registered AgnesCode provider —
12
+ * "third upstream provider" (ROADMAP §6.3).
13
+ *
14
+ * Mirrors `raccoon-llm-adapter.ts`'s shape: ONE `PiAiAdapter` carrying ONE
15
+ * profile (`sensenova-agnescode` → the credential's per-account BFF base), an
16
+ * INERT pi-ai auth plane (the AgnesCode JWT is resolved per request from the
17
+ * plugin's own credential store, pi-ai never manufactures it), and no token
18
+ * baked into the profile — the picker advertises models without one and a
19
+ * request fails at resolve time, where the panel status is visible.
20
+ *
21
+ * IMAGE hooks are deliberately NOT wired in v1: every catalogue row declares
22
+ * `model_type: "text"` with no image modality fact (ROADMAP §6.3), so there
23
+ * is nothing honest to advertise — a vision answer here would be an invented
24
+ * fact. If the platform later declares image input, this is the one place to
25
+ * add the hooks (mirror `raccoon-llm-adapter.ts`).
26
+ *
27
+ * The descriptor mapping lives in the peer-free `agnescode-models.ts`, so this
28
+ * module holds only assembly against the runtime and is exercised by the
29
+ * wiring/e2e checks, exactly as `llm-adapter.ts` is.
30
+ *
31
+ * @module dsh-connect-agnes-token-plan/agnescode-llm-adapter
32
+ */
33
+ /** Idle ceiling while one stream read is outstanding (dsh-llm-pi-ai default). */
34
+ const STREAM_IDLE_TIMEOUT_MS = 3e5;
35
+ /**
36
+ * Inert pi-ai auth plane.
37
+ *
38
+ * Authentication goes through `resolveApiKey` (the stored AgnesCode JWT, read
39
+ * per request from `agnescode-store.ts`) — pi-ai's own credential lifecycle
40
+ * must never manufacture a credential for this route, so every ambient
41
+ * question answers "nothing stored, nothing set".
42
+ */
43
+ const INERT_AUTH = {
44
+ credentials: {
45
+ async read() {},
46
+ async list() {
47
+ return [];
48
+ },
49
+ async modify() {},
50
+ async delete() {}
51
+ },
52
+ authContext: {
53
+ async env() {},
54
+ async fileExists() {
55
+ return false;
56
+ }
57
+ }
58
+ };
59
+ /**
60
+ * Assemble the AgnesCode adapter for one credential/catalogue snapshot.
61
+ *
62
+ * A fresh instance per rebuild is deliberate: `PiAiAdapter` memoizes the
63
+ * profiles snapshot internally, so the caller REPLACES the registered
64
+ * adapter when the credential or catalogue changes and emits
65
+ * `llm/adapters-updated`.
66
+ * @param {object} options - wiring.
67
+ * @param {object[]} [options.rows] - the AgnesCode roster rows (`agnescodeRoster`).
68
+ * @param {string} options.bffBase - the credential's pinned per-account BFF base.
69
+ * @param {() => Promise<string>} options.resolveToken - resolves the live
70
+ * AgnesCode JWT per request (re-harvested by the caller when expired).
71
+ * @param {(service: string) => unknown} [options.get] - service resolver
72
+ * (reserved for the image hooks when the platform declares image input).
73
+ * @returns {{adapter: object, providerIds: string[]}} the adapter and the ids
74
+ * it owns.
75
+ */
76
+ function createAgnescodeAdapter({ rows, bffBase, resolveToken, get } = {}) {
77
+ const models = buildAgnescodeDescriptors(rows ?? agnescodeRoster(null), { bffBase: String(bffBase ?? "") });
78
+ const provider = {
79
+ ...createProvider({
80
+ id: AGNESCODE_PROVIDER_ID,
81
+ name: AGNESCODE_DISPLAY_NAME,
82
+ auth: { apiKey: {
83
+ name: "AgnesCode access token",
84
+ async resolve({ credential } = {}) {
85
+ const token = credential?.key;
86
+ return token === void 0 || token === "" ? void 0 : {
87
+ auth: { apiKey: token },
88
+ source: AGNESCODE_DISPLAY_NAME
89
+ };
90
+ }
91
+ } },
92
+ models,
93
+ api: openAICompletionsApi()
94
+ }),
95
+ getModels: () => models
96
+ };
97
+ const profiles = /* @__PURE__ */ new Map([[AGNESCODE_PROVIDER_ID, {
98
+ provider: AGNESCODE_PROVIDER_ID,
99
+ displayName: AGNESCODE_DISPLAY_NAME,
100
+ streamIdleTimeoutMs: STREAM_IDLE_TIMEOUT_MS,
101
+ retryPolicy: resolveRetryPolicy(buildRetryPolicyConfig(), `${name}.${AGNESCODE_PROVIDER_ID}.retryPolicy`),
102
+ configuredMaxTokens: /* @__PURE__ */ new Map(),
103
+ modelErrors: /* @__PURE__ */ new Map(),
104
+ piProvider: provider
105
+ }]]);
106
+ const inner = new PiAiAdapter({
107
+ profiles: () => profiles,
108
+ auth: INERT_AUTH,
109
+ resolveApiKey: async () => resolveToken(),
110
+ resolveAttachments: () => get?.("attachments"),
111
+ resolveImageAccess: void 0
112
+ });
113
+ return {
114
+ adapter: new Proxy(inner, { get(target, prop, receiver) {
115
+ const value = Reflect.get(target, prop, receiver);
116
+ if (prop === "stream") return (options) => reclassifyStream(target.stream(options));
117
+ if (typeof value === "function" && prop === "prepareCall") return (...args) => {
118
+ const prepared = value.apply(target, args);
119
+ if (prepared && typeof prepared.then === "function") return prepared.then((p) => p && typeof p.stream === "function" ? {
120
+ ...p,
121
+ stream: (o) => reclassifyStream(p.stream(o))
122
+ } : p);
123
+ return prepared && typeof prepared.stream === "function" ? {
124
+ ...prepared,
125
+ stream: (o) => reclassifyStream(prepared.stream(o))
126
+ } : prepared;
127
+ };
128
+ return value;
129
+ } }),
130
+ providerIds: [AGNESCODE_PROVIDER_ID]
131
+ };
132
+ }
133
+
134
+ //#endregion
135
+ export { createAgnescodeAdapter };