@d-ai/magpie 0.0.0-stage → 0.1.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 (3) hide show
  1. package/README.md +64 -2
  2. package/index.mjs +428 -0
  3. package/package.json +32 -4
package/README.md CHANGED
@@ -1,3 +1,65 @@
1
- # Temporary Holding Version
1
+ # D.AI for magpie / OpenCode
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Use your [D.AI](https://dai.itbug.shop) models — GPT-6, Claude Opus 5.5, Grok 4.7,
4
+ DeepSeek V4.1, GLM 5.3, Gemini 3.8 — from any agent that speaks through
5
+ [magpie](https://usemagpie.ai) or OpenCode.
6
+
7
+ One `ddsk_…` API key is one account, and magpie keeps several.
8
+
9
+ ## Add it
10
+
11
+ ```sh
12
+ magpie plugin add @d-ai/magpie # from npm
13
+ magpie plugin add ./sdks/d-ai-magpie # from a folder, for development
14
+ magpie plugin login dai # asks for the API key
15
+ magpie provider test dai # one tiny call per API
16
+ ```
17
+
18
+ Then pick a model anywhere magpie does: `dai/claude-opus-5-5`,
19
+ `dai/gpt-6-astra`, `dai/deepseek-v4.1-flash`, …
20
+
21
+ The key comes from the D.AI console (`/#/apikeys`). Custom deployments can point
22
+ at another host with `magpie plugin options dai '{"baseUrl":"https://dai.example"}'`
23
+ or `DAI_BASE_URL`.
24
+
25
+ ## What it exposes
26
+
27
+ 15 token-metered chat models, in two APIs:
28
+
29
+ | API | Models | Path |
30
+ | --- | --- | --- |
31
+ | OpenAI chat completions | `deepseek-v4.1-flash`, `gemini-3.8-flash`, `glm-5.3-flash`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-6-astra`, `gpt-6-sol`, `grok-4.6`, `grok-4.7` | `/v1/chat/completions` |
32
+ | Anthropic Messages | `claude-fable-5`, `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-opus-5-5` | `/v1/messages` |
33
+
34
+ The model list and the prices come from D.AI's public catalog on every start
35
+ (`GET /api/system/public/capability-docs`), narrowed to the models that key may
36
+ call (`GET /v1/models`), so a model you are not entitled to never shows up and a
37
+ new model appears without a plugin update. A built-in list is used when D.AI
38
+ cannot be reached while the provider is first configured.
39
+
40
+ Every model reports its context window, its output cap, its USD/1M-token prices
41
+ and its thinking levels. Image input is marked for the families D.AI serves with
42
+ vision (GPT-5/6, Claude, Gemini, Grok, DeepSeek, GLM).
43
+
44
+ ## Usage
45
+
46
+ `magpie quota` shows the account behind the key: the wallet balance, the tier
47
+ and its cycle, how much of the balance this key alone may spend, and how many
48
+ calls it made today. That comes from `GET /api/ai/account`, an endpoint
49
+ authenticated by the `ddsk_` key itself.
50
+
51
+ A D.AI key has no plan window — it spends the account's shared wallet — so the
52
+ card reads as a balance, not a quota window. Two keys of one account show the
53
+ same balance but their own reservations and call counts.
54
+
55
+ ## Notes
56
+
57
+ - Only chat models. D.AI's image, video, music and ASR models are separate
58
+ capabilities with their own endpoints; agents here want a chat completion.
59
+ - The usage card needs `GET /api/ai/account` on the D.AI deployment. Against an
60
+ older one (404) the plugin falls back to the key's call log and shows call
61
+ counts instead of a balance.
62
+ - The plugin never rewrites request bodies. Thinking levels are offered as
63
+ model variants, and the gateway passes `reasoning_effort` through.
64
+ - Requests go out with `Authorization: Bearer <key>` plus `x-api-key`, so
65
+ either D.AI route works.
package/index.mjs ADDED
@@ -0,0 +1,428 @@
1
+ // D.AI (dai.itbug.shop) as a magpie / OpenCode provider plugin.
2
+ //
3
+ // One D.AI API key (`ddsk_…`) signs an account in; magpie may hold several
4
+ // under the same provider. Models come from D.AI's public capability catalog
5
+ // (`GET /api/system/public/capability-docs`), narrowed to what the key may
6
+ // actually call (`GET /v1/models`). Chat models run on D.AI's
7
+ // OpenAI-compatible `/v1/chat/completions`, Claude models on `/v1/messages`.
8
+ // `auth.usage` reads the account behind the key (`GET /api/ai/account`): the
9
+ // wallet balance, the tier, and how much this key alone may spend.
10
+ //
11
+ // `auth.usage` reads `GET /api/ai/account`, a `ddsk_`-authenticated endpoint
12
+ // added for exactly this: the wallet balance, the tier, and what the key has
13
+ // used. The older JWT-only endpoints (`/api/usd/me`, `/api/user/usage-stats`)
14
+ // cannot serve a plugin, and the key's call log only counts calls, not money.
15
+ //
16
+ // Facts the tables below encode (checked 2026-10-15):
17
+ // - The capability codes carrying token-metered chat are
18
+ // `openai_chat_completions` (10 models) and `claude_messages` (5 models).
19
+ // - `/v1/models` takes the API key as `Authorization: Bearer` or `x-api-key`
20
+ // and lists only the models that key may use.
21
+ // - `prepay_completion_token_limit` is D.AI's completion cap and the value a
22
+ // client should send as its max output tokens (`sdks/pi/src/discover.ts`).
23
+ // - Thinking levels and image input mirror `sdks/pi/src/thinking.ts`, the
24
+ // only place these were verified against the upstreams behind D.AI. A model
25
+ // absent from both tables is declared text-only without thinking levels
26
+ // until someone checks it. D.AI's own catalog is not consulted for either:
27
+ // it carries no per-model reasoning or modality metadata.
28
+ //
29
+ // Sections: constants · helpers · catalog · plugin.
30
+
31
+ const PROVIDER = "dai";
32
+ const DEFAULT_BASE = "https://dai.itbug.shop";
33
+ const OPENAI_NPM = "@ai-sdk/openai-compatible";
34
+ const ANTHROPIC_NPM = "@ai-sdk/anthropic";
35
+ const CATALOG_TTL_MS = 5 * 60 * 1000;
36
+ const REQUEST_TIMEOUT_MS = 20_000;
37
+
38
+ /** Capability codes whose models are token-metered chat models. */
39
+ const CHAT_CAPABILITIES = new Set(["openai_chat_completions", "claude_messages"]);
40
+
41
+ /** Thinking levels per model, verified against pi's tables. Absent = none offered. */
42
+ const VARIANTS = {
43
+ // GPT-5.6: `off` is `none`, no `minimal`; the upstream takes the rest.
44
+ "gpt-5.6-luna": ["none", "low", "medium", "high", "xhigh", "max"],
45
+ "gpt-5.6-sol": ["none", "low", "medium", "high", "xhigh", "max"],
46
+ "gpt-5.6-terra": ["none", "low", "medium", "high", "xhigh", "max"],
47
+ // GPT-6 cannot turn thinking off.
48
+ "gpt-6-astra": ["low", "medium", "high", "xhigh", "max"],
49
+ "gpt-6-sol": ["low", "medium", "high", "xhigh", "max"],
50
+ // River rejects minimal, medium and xhigh on DeepSeek-V4.1-Flash; `none` is off.
51
+ "deepseek-v4.1-flash": ["none", "low", "high", "max"],
52
+ // GLM-5.3-Flash cannot disable reasoning; River also rejects minimal/medium/xhigh.
53
+ "glm-5.3-flash": ["low", "high", "max"],
54
+ // Claude: the adaptive-effort models take xhigh/max, Opus 5.5 the full range.
55
+ "claude-fable-5": ["xhigh", "max"],
56
+ "claude-opus-4-7": ["xhigh", "max"],
57
+ "claude-opus-4-8": ["xhigh", "max"],
58
+ "claude-opus-5": ["xhigh", "max"],
59
+ "claude-opus-5-5": ["low", "medium", "high", "xhigh", "max"],
60
+ };
61
+
62
+ /**
63
+ * Models that take images as well as text, same set pi's tables mark.
64
+ * Gemini, Grok and GPT-5.6 each carry `cache_write_usd_per_1m_tokens: null`
65
+ * on D.AI today, so a null there reads as "no cache write price", not as a
66
+ * missing check: only listed, non-zero rates become a model's cache price.
67
+ */
68
+ const VISION = /^(?:gpt-5|gpt-6|deepseek-|glm-|claude-)/u;
69
+
70
+ /**
71
+ * Offline fallback, in the shape the network path builds, for a magpie that
72
+ * cannot reach D.AI while the provider is first configured.
73
+ * `[id, context, output, input USD/1M, output USD/1M, cache-read USD/1M]`.
74
+ */
75
+ const FALLBACK_ROWS = [
76
+ ["deepseek-v4.1-flash", 1_048_576, 128_000, 0.15, 0.6, 0.003],
77
+ ["gemini-3.8-flash", 1_050_000, 128_000, 0.5, 3, 0.05],
78
+ ["glm-5.3-flash", 1_048_576, 128_000, 0.15, 0.5, 0.03],
79
+ ["gpt-5.6-luna", 1_050_000, 128_000, 0.2, 1.2, 0.02],
80
+ ["gpt-5.6-sol", 1_050_000, 128_000, 5, 30, 0.5],
81
+ ["gpt-5.6-terra", 1_050_000, 128_000, 2, 12, 0.2],
82
+ ["gpt-6-astra", 1_050_000, 128_000, 10, 50, 1],
83
+ ["gpt-6-sol", 1_050_000, 128_000, 2, 10, 0.2],
84
+ ["grok-4.6", 500_000, 128_000, 1.5, 5, 0.4],
85
+ ["grok-4.7", 500_000, 128_000, 1.5, 5, 0.4],
86
+ ["claude-fable-5", 1_000_000, 128_000, 20, 100, 2],
87
+ ["claude-opus-4-7", 1_000_000, 128_000, 10, 50, 1],
88
+ ["claude-opus-4-8", 1_000_000, 128_000, 10, 50, 1],
89
+ ["claude-opus-5", 1_000_000, 128_000, 10, 50, 1],
90
+ ["claude-opus-5-5", 1_000_000, 128_000, 8, 40, 0.4],
91
+ ];
92
+
93
+ // ---- helpers -------------------------------------------------------------------
94
+
95
+ const textOf = (value) => (typeof value === "string" && value.trim() ? value.trim() : "");
96
+ const numberOr = (value, fallback) =>
97
+ typeof value === "number" && Number.isFinite(value) ? value : fallback;
98
+ const optionalNumber = (value) =>
99
+ typeof value === "number" && Number.isFinite(value) ? value : undefined;
100
+ const positiveNumber = (value) =>
101
+ typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
102
+
103
+ /**
104
+ * A USD amount as magpie's cards say one: whole dollars plain, otherwise the
105
+ * significant decimals without a trail of zeros ($86.952653 -> "86.952653").
106
+ * Six places is D.AI's scale, so nothing is rounded away that matters.
107
+ */
108
+ function usd(value) {
109
+ const n = Number(value);
110
+ if (!Number.isFinite(n)) return "0";
111
+ return n.toFixed(6).replace(/\.?0+$/u, "") || "0";
112
+ }
113
+
114
+ /** The base URL with no trailing slash and no trailing `/v1`. */
115
+ function normalizeBase(raw) {
116
+ const value = textOf(raw);
117
+ if (!value) return undefined;
118
+ return value.replace(/\/+$/u, "").replace(/\/v1$/u, "");
119
+ }
120
+
121
+ const apiBase = (base) => `${base}/v1`;
122
+
123
+ const isClaudeModel = (id) => id.startsWith("claude-");
124
+
125
+ /** `ddsk_01ABC_secret` → what a card may show, never the secret itself. */
126
+ function keyLabel(key) {
127
+ const id = textOf(key).replace(/^ddsk_/u, "").split("_")[0];
128
+ return id ? `key ${id}` : "API key";
129
+ }
130
+
131
+ function variantsOf(id) {
132
+ const levels =
133
+ VARIANTS[id] ??
134
+ (/^gpt-5\.6/u.test(id)
135
+ ? VARIANTS["gpt-5.6-luna"]
136
+ : /^gpt-6/u.test(id)
137
+ ? VARIANTS["gpt-6-astra"]
138
+ : undefined);
139
+ if (!levels) return undefined;
140
+ return Object.fromEntries(levels.map((level) => [level, {}]));
141
+ }
142
+
143
+ /**
144
+ * An OpenCode `Model`, which is also what magpie's `config` hook wants. Both
145
+ * `api` and `provider` are written because the two documents disagree on which
146
+ * one carries a per-model API override; they hold the same values, so either
147
+ * reader sees the same thing.
148
+ */
149
+ function buildModel(id, base, rate = {}) {
150
+ const rules = rate.billing_rules ?? {};
151
+ const vision = VISION.test(id);
152
+ const variants = variantsOf(id);
153
+ const npm = isClaudeModel(id) ? ANTHROPIC_NPM : OPENAI_NPM;
154
+ const cost = {
155
+ input: numberOr(rate.input_usd_per_1m_tokens, 0),
156
+ output: numberOr(rate.output_usd_per_1m_tokens, 0),
157
+ };
158
+ const cacheRead = positiveNumber(rules.cache_read_usd_per_1m_tokens);
159
+ if (cacheRead !== undefined) cost.cache_read = cacheRead;
160
+ const cacheWrite = positiveNumber(rules.cache_write_usd_per_1m_tokens);
161
+ if (cacheWrite !== undefined) cost.cache_write = cacheWrite;
162
+ return {
163
+ id,
164
+ name: id,
165
+ api: { id, npm, url: apiBase(base) },
166
+ provider: { npm, api: apiBase(base) },
167
+ limit: {
168
+ context: Math.max(1, numberOr(rate.context_window_tokens, 128_000)),
169
+ output: Math.max(1, numberOr(rate.prepay_completion_token_limit, 128_000)),
170
+ },
171
+ capabilities: {
172
+ reasoning: Boolean(variants) || vision,
173
+ toolcall: true,
174
+ attachment: vision,
175
+ input: vision ? { text: true, image: true } : { text: true },
176
+ },
177
+ modalities: { input: vision ? ["text", "image"] : ["text"] },
178
+ cost,
179
+ ...(variants ? { variants } : {}),
180
+ };
181
+ }
182
+
183
+ function fallbackCatalog(base) {
184
+ const models = {};
185
+ for (const [id, context, output, input, out, cacheRead] of FALLBACK_ROWS) {
186
+ models[id] = buildModel(id, base, {
187
+ context_window_tokens: context,
188
+ prepay_completion_token_limit: output,
189
+ input_usd_per_1m_tokens: input,
190
+ output_usd_per_1m_tokens: out,
191
+ billing_rules: { cache_read_usd_per_1m_tokens: cacheRead },
192
+ });
193
+ }
194
+ return models;
195
+ }
196
+
197
+ // ---- catalog -------------------------------------------------------------------
198
+
199
+ async function fetchJson(url, { apiKey, signal } = {}) {
200
+ const controller = new AbortController();
201
+ const onAbort = () => controller.abort();
202
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
203
+ signal?.addEventListener("abort", onAbort, { once: true });
204
+ const headers = new Headers();
205
+ if (apiKey) {
206
+ headers.set("Authorization", `Bearer ${apiKey}`);
207
+ headers.set("x-api-key", apiKey);
208
+ }
209
+ try {
210
+ const res = await fetch(url, { headers, signal: controller.signal });
211
+ if (!res.ok) {
212
+ throw Object.assign(new Error(`D.AI answered ${res.status}`), { status: res.status });
213
+ }
214
+ return await res.json();
215
+ } finally {
216
+ clearTimeout(timer);
217
+ signal?.removeEventListener("abort", onAbort);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Every chat model D.AI publishes, keyed by model id. With a key the catalog is
223
+ * narrowed to the models that key may call; a `/v1/models` failure leaves the
224
+ * full catalog in place rather than hiding every model.
225
+ */
226
+ async function fetchCatalog(base, { key, signal } = {}) {
227
+ const docs = await fetchJson(`${base}/api/system/public/capability-docs`, { signal });
228
+ const models = {};
229
+ for (const capability of docs?.data ?? []) {
230
+ if (!CHAT_CAPABILITIES.has(capability?.capability_code)) continue;
231
+ for (const model of capability?.models ?? []) {
232
+ const id = textOf(model?.model_code);
233
+ if (!id || models[id]) continue;
234
+ models[id] = buildModel(id, base, model?.token_rate ?? {});
235
+ }
236
+ }
237
+ if (!key || !Object.keys(models).length) return models;
238
+ try {
239
+ const listed = await fetchJson(`${apiBase(base)}/models`, { apiKey: key, signal });
240
+ const allowed = new Set(
241
+ (listed?.data ?? []).map((model) => textOf(model?.id)).filter(Boolean),
242
+ );
243
+ if (!allowed.size) return models;
244
+ return Object.fromEntries(Object.entries(models).filter(([id]) => allowed.has(id)));
245
+ } catch {
246
+ return models;
247
+ }
248
+ }
249
+
250
+ // ---- the usage card ------------------------------------------------------------
251
+
252
+ /**
253
+ * What `GET /api/ai/account` says, as magpie's card: the wallet's total, then
254
+ * the tier, this key's own reservation, and today's calls.
255
+ */
256
+ function balanceCard(account, key) {
257
+ const balance = account?.balance ?? {};
258
+ const self = account?.api_key ?? {};
259
+ const tier = account?.tier ?? {};
260
+ const parts = [`$${usd(optionalNumber(balance.total) ?? 0)} left`];
261
+ if (textOf(tier.code)) {
262
+ parts.push(
263
+ [
264
+ textOf(tier.code),
265
+ textOf(tier.billing_cycle),
266
+ optionalNumber(tier.expires_in_days) === undefined
267
+ ? undefined
268
+ : `${tier.expires_in_days}d left`,
269
+ ]
270
+ .filter(Boolean)
271
+ .join(" · "),
272
+ );
273
+ }
274
+ // Only a key with a budget envelope has a reservation; the shared pool has none.
275
+ if (self.envelope_active) {
276
+ const reserved =
277
+ (optionalNumber(self.reserved_package_usd) ?? 0) +
278
+ (optionalNumber(self.reserved_recharge_usd) ?? 0);
279
+ if (reserved > 0) parts.push(`this key $${usd(reserved)}`);
280
+ }
281
+ const calls = optionalNumber(account?.tokens?.today?.calls);
282
+ if (calls !== undefined) {
283
+ parts.push(`${calls} call${calls === 1 ? "" : "s"} today`);
284
+ }
285
+ return {
286
+ plan: textOf(tier.code) || keyLabel(key),
287
+ windows: [{ name: "D.AI balance", display: parts.join(" · ") }],
288
+ };
289
+ }
290
+
291
+ /**
292
+ * The older card, for a D.AI that has not been updated yet: the key's own call
293
+ * log (`GET /api/ai/usage-logs/api-key`) counts calls but knows no balance.
294
+ */
295
+ async function callLogCard(base, key) {
296
+ const log = await fetchJson(`${base}/api/ai/usage-logs/api-key?page=1&page_size=1`, {
297
+ apiKey: key,
298
+ });
299
+ const total = numberOr(log?.total, 0);
300
+ const last = Array.isArray(log?.data) ? log.data[0] : undefined;
301
+ const parts = [`${total} call${total === 1 ? "" : "s"}`];
302
+ if (last) {
303
+ const settled = optionalNumber(last.settled_usd);
304
+ parts.push(
305
+ [
306
+ textOf(last.created_at).replace("T", " ").slice(0, 16),
307
+ textOf(last.model_code),
308
+ textOf(last.status),
309
+ settled === undefined ? undefined : `$${usd(settled)}`,
310
+ ]
311
+ .filter(Boolean)
312
+ .join(" · "),
313
+ );
314
+ }
315
+ return {
316
+ plan: keyLabel(key),
317
+ windows: [{ name: "D.AI calls", display: parts.join(" · ") }],
318
+ };
319
+ }
320
+
321
+ // ---- plugin --------------------------------------------------------------------
322
+
323
+ const DaiPlugin = async (_input, options = {}) => {
324
+ const base = normalizeBase(options?.baseUrl ?? process.env.DAI_BASE_URL) ?? DEFAULT_BASE;
325
+ const offline = fallbackCatalog(base);
326
+
327
+ // One catalog read per account, reused until it goes stale. Keyed by the
328
+ // API key, never module-global: magpie runs every account of a provider
329
+ // through this one plugin instance.
330
+ const catalogs = new Map();
331
+ const catalogFor = async (key, signal) => {
332
+ const cached = catalogs.get(key);
333
+ if (cached && Date.now() - cached.at < CATALOG_TTL_MS) return cached.models;
334
+ const models = await fetchCatalog(base, { key, signal });
335
+ if (Object.keys(models).length) {
336
+ catalogs.set(key, { at: Date.now(), models });
337
+ return models;
338
+ }
339
+ return undefined;
340
+ };
341
+
342
+ return {
343
+ // Declare the provider so it exists before anyone signs in; `??=` keeps
344
+ // whatever the user configured for it in plugins.json.
345
+ config: async (cfg) => {
346
+ cfg.provider ??= {};
347
+ cfg.provider[PROVIDER] ??= {
348
+ name: "D.AI",
349
+ npm: OPENAI_NPM,
350
+ api: apiBase(base),
351
+ models: offline,
352
+ };
353
+ },
354
+
355
+ auth: {
356
+ provider: PROVIDER,
357
+
358
+ methods: [{ type: "api", label: "D.AI API key", placeholder: "ddsk_…" }],
359
+
360
+ async loader(getAuth) {
361
+ const auth = await getAuth();
362
+ const key = textOf(auth?.key);
363
+ if (auth?.type !== "api" || !key) return {};
364
+ return {
365
+ baseURL: apiBase(base),
366
+ apiKey: key,
367
+ headers: { "x-dai-client": "magpie/0.1.0" },
368
+ };
369
+ },
370
+
371
+ // The account behind the key: wallet balance, tier and token totals,
372
+ // from D.AI's own endpoint (`GET /api/ai/account`). A `ddsk_` key may
373
+ // call it, which is the point: the plugin can show what a D.AI key
374
+ // cannot otherwise read. The card says the balance as the wallet's
375
+ // total, then how much of it this key may spend and what it has used.
376
+ async usage(getAuth) {
377
+ const auth = await getAuth();
378
+ const key = textOf(auth?.key);
379
+ if (auth?.type !== "api" || !key) {
380
+ return { windows: [], error: "D.AI: sign in with an API key" };
381
+ }
382
+ try {
383
+ const account = await fetchJson(`${base}/api/ai/account`, { apiKey: key });
384
+ return balanceCard(account, key);
385
+ } catch (error) {
386
+ const status = error?.status;
387
+ if (status === 401 || status === 403) {
388
+ return { windows: [], error: "D.AI refused the API key", signIn: "expired" };
389
+ }
390
+ // A deployment older than the endpoint answers 404. Its call log
391
+ // is still there, and counting calls beats showing an error.
392
+ if (status === 404) {
393
+ try {
394
+ return await callLogCard(base, key);
395
+ } catch (fallbackError) {
396
+ return { windows: [], error: String(fallbackError?.message ?? fallbackError) };
397
+ }
398
+ }
399
+ return { windows: [], error: String(error?.message ?? error) };
400
+ }
401
+ },
402
+ },
403
+
404
+ provider: {
405
+ id: PROVIDER,
406
+
407
+ // The account's own list when D.AI answers, else the catalog the
408
+ // config hook declared, so a failed fetch never empties the provider.
409
+ async models(provider, { auth } = {}) {
410
+ const key = textOf(auth?.key);
411
+ if (!key) return provider.models;
412
+ try {
413
+ const models = await catalogFor(key, undefined);
414
+ return models ?? provider.models;
415
+ } catch {
416
+ return provider.models;
417
+ }
418
+ },
419
+ },
420
+ };
421
+ };
422
+
423
+ // Only the plugin may be a function export: magpie calls every exported
424
+ // function as a plugin, and an OpenCode v1 module is `{ id, server }` anyway.
425
+ export default { id: PROVIDER, server: DaiPlugin };
426
+
427
+ /** For tests and manual probing: the pure pieces, never called as a plugin. */
428
+ export const _internal = { buildModel, fallbackCatalog, fetchCatalog, keyLabel, normalizeBase };
package/package.json CHANGED
@@ -1,6 +1,34 @@
1
1
  {
2
2
  "name": "@d-ai/magpie",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "D.AI (dai.itbug.shop) models in magpie and OpenCode: one ddsk_ API key, 15 chat models, per-key call usage",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "files": [
11
+ "index.mjs",
12
+ "README.md"
13
+ ],
14
+ "keywords": [
15
+ "opencode",
16
+ "opencode-plugin",
17
+ "magpie",
18
+ "dai",
19
+ "d-ai"
20
+ ],
21
+ "license": "MIT",
22
+ "homepage": "https://dai.itbug.shop",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/mdddj/dd_server_rust_private.git",
26
+ "directory": "sdks/d-ai-magpie"
27
+ },
28
+ "magpie": {
29
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4IiByb2xlPSJpbWciIGFyaWEtbGFiZWxsZWRieT0iZGFpLWNsb3VkLXRpdGxlIGRhaS1jbG91ZC1kZXNjIj4KICA8dGl0bGUgaWQ9ImRhaS1jbG91ZC10aXRsZSI+RC5BSSBjbG91ZCBtYXNjb3Q8L3RpdGxlPgogIDxkZXNjIGlkPSJkYWktY2xvdWQtZGVzYyI+QSBmcmllbmRseSByb3VuZGVkIGNsb3VkIGNoYXJhY3RlciBvbiBhIGJsYWNrIHNxdWlyY2xlLjwvZGVzYz4KICA8cmVjdCB4PSI0IiB5PSI0IiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCIgcng9IjM4IiBmaWxsPSIjMDAwMDAwIiAvPgogIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDExLjUgOCkgc2NhbGUoLjgyKSI+CiAgICA8cGF0aCBmaWxsPSIjRjVGNUY1IiBkPSJNMjcgMTI0QzE2LjUgMTI0IDggMTE1LjUgOCAxMDVDOCA5NS4zIDE1LjMgODcuMyAyNC43IDg2LjFDMjUuMSA2My4xIDQwIDQ0IDU4LjQgNDRDNzIuMSA0NCA4My45IDUyLjIgODkuMSA2NEMxMDUuNyA2NC4xIDExOSA3Ny41IDExOSA5NEMxMTkgMTEwLjYgMTA1LjYgMTI0IDg5IDEyNEgyN1oiIC8+CiAgICA8Y2lyY2xlIGN4PSI1MSIgY3k9IjkwIiByPSI1IiBmaWxsPSIjMDAwMDAwIiAvPgogICAgPGNpcmNsZSBjeD0iNzgiIGN5PSI5MCIgcj0iNSIgZmlsbD0iIzAwMDAwMCIgLz4KICA8L2c+Cjwvc3ZnPgo="
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }