@vincemakes/kiso-provider-openai 0.1.29 → 0.1.31

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/README.md CHANGED
@@ -12,7 +12,7 @@ overview.
12
12
 
13
13
  `KISO_DUMP_REQUESTS=<dir>` writes every outgoing request body to
14
14
  `<dir>/req-<pid>-<n>.json` before it is sent — the diagnosis instrument
15
- for the request-prefix (D 区) contract, kept as a permanent debug sink.
15
+ for the request-prefix (D area) contract, kept as a permanent debug sink.
16
16
  ⚠ The bodies are REAL conversation data (the model may have seen repo
17
17
  contents) — never share a dump dir; a dump failure never breaks the
18
18
  request. `bench/dumpdiff.py` byte-diffs consecutive dumps and localizes
package/dist/index.d.ts CHANGED
@@ -16,13 +16,13 @@
16
16
  import OpenAI from "openai";
17
17
  import type { ChatCompletionChunk } from "openai/resources/chat/completions";
18
18
  import type { Adapter } from "@vincemakes/kiso-core";
19
- /** Config accepted by the high-level factory (七: the provider owns its SDK). */
19
+ /** Config accepted by the high-level factory (round 7: the provider owns its SDK). */
20
20
  export interface OpenAICompatProviderConfig {
21
21
  readonly apiKey?: string;
22
22
  readonly baseUrl?: string;
23
23
  }
24
24
  /**
25
- * High-level factory (七): builds the adapter FROM CONFIG, owning the SDK
25
+ * High-level factory (round 7): builds the adapter FROM CONFIG, owning the SDK
26
26
  * inside this package. Consumers (and the runtime's lazy provider path)
27
27
  * import ONLY @vincemakes/kiso-provider-openai — the SDK stays a private dependency
28
28
  * of this package, so nested installs resolve it next to here, never
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import { join } from "node:path";
18
18
  import OpenAI from "openai";
19
19
  import { mapApiError } from "@vincemakes/kiso-core";
20
20
  /**
21
- * High-level factory (七): builds the adapter FROM CONFIG, owning the SDK
21
+ * High-level factory (round 7): builds the adapter FROM CONFIG, owning the SDK
22
22
  * inside this package. Consumers (and the runtime's lazy provider path)
23
23
  * import ONLY @vincemakes/kiso-provider-openai — the SDK stays a private dependency
24
24
  * of this package, so nested installs resolve it next to here, never
@@ -104,7 +104,7 @@ export function createOpenAICompatAdapter(client) {
104
104
  for (const tc of delta?.tool_calls ?? []) {
105
105
  const buffered = pending.get(tc.index);
106
106
  if (!buffered) {
107
- // 六: no fallback id is adopted yet — the id must
107
+ // round 6: no fallback id is adopted yet — the id must
108
108
  // arrive from the provider to become the identity.
109
109
  pending.set(tc.index, {
110
110
  index: tc.index,
@@ -122,7 +122,7 @@ export function createOpenAICompatAdapter(client) {
122
122
  if (tc.function?.name)
123
123
  call.name = tc.function.name;
124
124
  if (tc.id) {
125
- // 九: the FIRST non-empty id is the call's identity,
125
+ // round 9: the FIRST non-empty id is the call's identity,
126
126
  // forever. A DIFFERENT id later is a protocol
127
127
  // violation — a structured error, never a silent
128
128
  // switch (start/delta/end must share one identity).
@@ -136,7 +136,7 @@ export function createOpenAICompatAdapter(client) {
136
136
  call.id = tc.id;
137
137
  // The identity is now known: emit the start, then
138
138
  // flush the argument deltas that arrived before it
139
- // under the SAME id (六: start → delta → end all
139
+ // under the SAME id (round 6: start → delta → end all
140
140
  // share one identity, never the fallback).
141
141
  if (!call.emittedStart) {
142
142
  call.emittedStart = true;
@@ -174,7 +174,7 @@ export function createOpenAICompatAdapter(client) {
174
174
  }
175
175
  if (chunk.usage) {
176
176
  usageSent = true;
177
- // 六: REAL cached-token data is read from the provider's
177
+ // round 6: REAL cached-token data is read from the provider's
178
178
  // prompt_tokens_details — an absent value is null, NEVER
179
179
  // faked as a zero-cache turn. OpenAI does not report a
180
180
  // cache write; null is the honest answer.
@@ -210,7 +210,7 @@ export function createOpenAICompatAdapter(client) {
210
210
  catch {
211
211
  input = null; // never a silent repair
212
212
  }
213
- // 六: a call whose id NEVER arrived adopts the index fallback
213
+ // round 6: a call whose id NEVER arrived adopts the index fallback
214
214
  // here — no start/delta was emitted under any other identity,
215
215
  // so this end is the call's first and only identity.
216
216
  yield {
@@ -293,7 +293,7 @@ function toOpenAIContent(content) {
293
293
  ? { type: "text", text: block.text }
294
294
  : {
295
295
  type: "image_url",
296
- // 六: a base64 block becomes a REAL data URL —
296
+ // round 6: a base64 block becomes a REAL data URL —
297
297
  // `data:<media>;base64,<data>` — never an empty string URL.
298
298
  // URL-sourced blocks pass the provider URL through.
299
299
  image_url: {
@@ -304,7 +304,7 @@ function toOpenAIContent(content) {
304
304
  });
305
305
  }
306
306
  /**
307
- * 六: OpenAI tool results accept TEXT ONLY — an image block is converted to
307
+ * round 6: OpenAI tool results accept TEXT ONLY — an image block is converted to
308
308
  * an explicit, honest text note (what kind of image was omitted and why),
309
309
  * never silently dropped.
310
310
  */
@@ -325,20 +325,20 @@ function toOpenAIMessages(messages, systemPrompt) {
325
325
  if (systemPrompt !== undefined) {
326
326
  out.push({ role: "system", content: systemPrompt });
327
327
  }
328
- // 合并轮 (0.1.23) C7 修订: `reasoning_content` 的存在性由整个投影的
329
- // 单调状态决定 — 投影里存在任一 reasoning → 每条 assistant 消息都
330
- // 携带(自身 reasoning,或 "");否则一条都不带。理由: D 区请求级
331
- // 字节稳定。旧实现按"当前轮"判定(手感批 C7),轮边界一过,旧轮
332
- // assistant 消息的字段被剥掉,序列化被改写 — 请求 N 与 N+1 的公共
333
- // 前缀断在旧消息处,provider 前缀缓存每轮边界断一次(fresh 之谜
334
- // 实证: 14 请求的会话里两次缓存断点都在轮边界;修复后同会话 0 断
335
- // 点、逐请求 cached 82-98%)。单调性保证存在性在会话内只翻转一次
336
- // (首个 thinking 出现时,通常在首轮 — 此前几乎没有旧 assistant
337
- // 消息),之后从生到死不翻转;真 OpenAI 永不产生 reasoning → 字段
338
- // 永不出现,其请求路径与旧行为逐字节相同。带 reasoning 的旧轮消息
339
- // 回传其推理是 DeepSeek 官方推荐的缓存稳定形态;旧内容命中前缀
340
- // 缓存只按 0.1× 计费,"回传是 token 浪费"的前提在缓存经济下不成立。
341
- // "" 字段在旧轮同样被接受(真 API 验证: 200 + 2560 cached tokens)。
328
+ // the merge round (0.1.23) C7 revision: `reasoning_content`'s PRESENCE follows the whole projection's
329
+ // monotone state — any reasoning in the projection → every assistant message
330
+ // carries it (its own reasoning, or ""); otherwise none do. Rationale: the D-area request-level
331
+ // byte stability. The old implementation keyed on the "current round" (the ergonomics batch C7); once a round boundary passed,
332
+ // the old rounds' assistant-message fields were stripped and the serialization rewritten — requests N and N+1's common
333
+ // prefix broke at the old message; the provider's prefix cache broke at every round boundary (the fresh-mystery
334
+ // empirical proof: in a 14-request session both cache breaks sat at round boundaries; after the fix the same session had 0 breaks,
335
+ // per-request cached 82-98%). Monotonicity guarantees the field's presence flips at most once per session
336
+ // (when the first thinking appears, usually in the first round — before it there are almost no old assistant
337
+ // messages); after it, never flips for the session's life; real OpenAI never produces reasoning → the field
338
+ // never appears, and its request path is byte-for-byte the old behavior. Old-round messages carrying reasoning
339
+ // pass their reasoning back — DeepSeek's officially recommended cache-stable shape; old content hitting the prefix
340
+ // cache bills at 0.1× only — the "repassing wastes tokens" premise does not hold under cache economics.
341
+ // the "" field is accepted on old rounds too (real API verification: 200 + 2560 cached tokens).
342
342
  const hasReasoning = messages.some((m) => m.role === "assistant" && m.reasoning !== undefined);
343
343
  for (const msg of messages) {
344
344
  if (msg.role === "user") {
@@ -372,7 +372,7 @@ function toOpenAIMessages(messages, systemPrompt) {
372
372
  else {
373
373
  // tool messages accept text only — images are converted to an
374
374
  // EXPLICIT text note (toOpenAIToolResultContent), never dropped
375
- // (六).
375
+ // (round 6).
376
376
  out.push({
377
377
  role: "tool",
378
378
  tool_call_id: msg.callId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-provider-openai",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "description": "kiso OpenAI-compatible adapter — OpenAI + the compat family (GLM, Kimi, DeepSeek, OpenRouter) via base_url swap, reasoning dialects digested.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  "test": "vitest run"
22
22
  },
23
23
  "dependencies": {
24
- "@vincemakes/kiso-core": "0.1.29",
24
+ "@vincemakes/kiso-core": "0.1.30",
25
25
  "openai": "^7.3.0"
26
26
  },
27
27
  "devDependencies": {