@justin06lee/yagami 0.4.1 → 0.6.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.
- package/README.md +141 -95
- package/dist/{chunk-M5UHR273.js → chunk-D2PNH6GV.js} +80 -20
- package/dist/chunk-D2PNH6GV.js.map +1 -0
- package/dist/{chunk-ASS6MJ7C.js → chunk-ZYHC7PXX.js} +887 -13
- package/dist/chunk-ZYHC7PXX.js.map +1 -0
- package/dist/cli.js +33 -8
- package/dist/cli.js.map +1 -1
- package/dist/{engine-pmCK3S7z.d.ts → hostConfig-HmZ3z297.d.ts} +114 -2
- package/dist/index.d.ts +207 -5
- package/dist/index.js +79 -3
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.js +4 -3
- package/package.json +2 -2
- package/dist/chunk-ASS6MJ7C.js.map +0 -1
- package/dist/chunk-M5UHR273.js.map +0 -1
|
@@ -108,6 +108,9 @@ function parseModelRef(model, providerIds) {
|
|
|
108
108
|
function qualifiedModel(providerId, model) {
|
|
109
109
|
return `${providerId}:${model}`;
|
|
110
110
|
}
|
|
111
|
+
function isSessionProvider(p) {
|
|
112
|
+
return typeof p.openSession === "function";
|
|
113
|
+
}
|
|
111
114
|
|
|
112
115
|
// src/core/executable.ts
|
|
113
116
|
import * as fs from "fs";
|
|
@@ -180,7 +183,7 @@ function resolveClaudeExecutable(explicit) {
|
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
// src/version.ts
|
|
183
|
-
var VERSION = "0.
|
|
186
|
+
var VERSION = "0.5.0";
|
|
184
187
|
|
|
185
188
|
// src/core/providers/acp.ts
|
|
186
189
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -439,6 +442,22 @@ var AcpProvider = class {
|
|
|
439
442
|
conn.close();
|
|
440
443
|
}
|
|
441
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* A live, interactive ACP session: the agent runs warm in the project
|
|
447
|
+
* directory with ITS OWN defaults — no plan-mode hardening, no forced
|
|
448
|
+
* config. Tool calls stream as normalized events and permission requests
|
|
449
|
+
* go to the host's handler, exactly as the agent's own client would ask.
|
|
450
|
+
*/
|
|
451
|
+
openSession(options) {
|
|
452
|
+
return new AcpAgentSession({
|
|
453
|
+
id: this.id,
|
|
454
|
+
modelConfigId: this.modelConfigId,
|
|
455
|
+
connect: (cwd) => this.connectImpl(cwd),
|
|
456
|
+
classify: (err, ctx) => this.classify(err, ctx),
|
|
457
|
+
selectModel: (conn, sessionId, configOptions, model) => this.selectModel(conn, sessionId, configOptions, model),
|
|
458
|
+
options
|
|
459
|
+
});
|
|
460
|
+
}
|
|
442
461
|
async selectModel(conn, sessionId, configOptions, model) {
|
|
443
462
|
const option = configOptions?.find((o) => o.id === this.modelConfigId) ?? configOptions?.find((o) => o.category === "model");
|
|
444
463
|
if (!option || option.type !== "select") {
|
|
@@ -495,6 +514,175 @@ var AcpProvider = class {
|
|
|
495
514
|
return `${plain ?? "unknown version"} \u26A0 no ACP handshake (${handshakeError ?? "unknown error"}) \u2014 too old, or a different program with the same name?`;
|
|
496
515
|
}
|
|
497
516
|
};
|
|
517
|
+
var AcpAgentSession = class {
|
|
518
|
+
constructor(cfg) {
|
|
519
|
+
this.cfg = cfg;
|
|
520
|
+
this.provider = cfg.id;
|
|
521
|
+
}
|
|
522
|
+
cfg;
|
|
523
|
+
provider;
|
|
524
|
+
conn;
|
|
525
|
+
sessionId;
|
|
526
|
+
queue = null;
|
|
527
|
+
opening;
|
|
528
|
+
closed = false;
|
|
529
|
+
costUsd;
|
|
530
|
+
/** Updates often omit title/kind — remember them from the start event. */
|
|
531
|
+
toolMeta = /* @__PURE__ */ new Map();
|
|
532
|
+
get id() {
|
|
533
|
+
return this.sessionId;
|
|
534
|
+
}
|
|
535
|
+
ensureOpen() {
|
|
536
|
+
this.opening ??= this.open();
|
|
537
|
+
return this.opening;
|
|
538
|
+
}
|
|
539
|
+
async open() {
|
|
540
|
+
const { options } = this.cfg;
|
|
541
|
+
const conn = await this.cfg.connect(options.cwd);
|
|
542
|
+
this.conn = conn;
|
|
543
|
+
let configOptions;
|
|
544
|
+
if (options.resume && supportsResume(conn.init)) {
|
|
545
|
+
const resumed = await conn.agent.resumeSession({ sessionId: options.resume, cwd: options.cwd }).catch((err) => {
|
|
546
|
+
throw this.cfg.classify(err);
|
|
547
|
+
});
|
|
548
|
+
this.sessionId = options.resume;
|
|
549
|
+
configOptions = resumed.configOptions;
|
|
550
|
+
} else {
|
|
551
|
+
const created = await conn.agent.newSession({ cwd: options.cwd, mcpServers: [] }).catch((err) => {
|
|
552
|
+
throw this.cfg.classify(err);
|
|
553
|
+
});
|
|
554
|
+
this.sessionId = created.sessionId;
|
|
555
|
+
configOptions = created.configOptions;
|
|
556
|
+
}
|
|
557
|
+
const sessionId = this.sessionId;
|
|
558
|
+
const mode = options.native?.mode;
|
|
559
|
+
if (mode) await conn.agent.setSessionMode({ sessionId, modeId: mode }).catch(() => {
|
|
560
|
+
});
|
|
561
|
+
if (options.model) await this.cfg.selectModel(conn, sessionId, configOptions, options.model);
|
|
562
|
+
conn.setHandlers({
|
|
563
|
+
onPermission: (p) => this.onPermission(p),
|
|
564
|
+
onUpdate: (n) => this.onUpdate(n)
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
async onPermission(p) {
|
|
568
|
+
const toolCall = p.toolCall;
|
|
569
|
+
const request = {
|
|
570
|
+
provider: this.provider,
|
|
571
|
+
...this.sessionId ? { sessionId: this.sessionId } : {},
|
|
572
|
+
tool: toolCall?.title ?? "tool",
|
|
573
|
+
...toolCall?.kind ? { kind: toolCall.kind } : {},
|
|
574
|
+
...toolCall?.title ? { title: toolCall.title } : {},
|
|
575
|
+
input: toolCall?.rawInput,
|
|
576
|
+
raw: p
|
|
577
|
+
};
|
|
578
|
+
let decision = "deny";
|
|
579
|
+
try {
|
|
580
|
+
decision = await this.cfg.options.permissions.decide(request);
|
|
581
|
+
} catch {
|
|
582
|
+
}
|
|
583
|
+
this.queue?.push({ type: "permission", request, decision });
|
|
584
|
+
const preferred = {
|
|
585
|
+
allow: ["allow_once", "allow_always"],
|
|
586
|
+
allow_always: ["allow_always", "allow_once"],
|
|
587
|
+
deny: ["reject_once", "reject_always"],
|
|
588
|
+
deny_always: ["reject_always", "reject_once"]
|
|
589
|
+
};
|
|
590
|
+
for (const kind of preferred[decision]) {
|
|
591
|
+
const option = p.options.find((o) => o.kind === kind);
|
|
592
|
+
if (option) return { outcome: { outcome: "selected", optionId: option.optionId } };
|
|
593
|
+
}
|
|
594
|
+
return rejectOption(p);
|
|
595
|
+
}
|
|
596
|
+
onUpdate(n) {
|
|
597
|
+
if (n.sessionId !== this.sessionId) return;
|
|
598
|
+
const u = n.update;
|
|
599
|
+
switch (u.sessionUpdate) {
|
|
600
|
+
case "agent_message_chunk":
|
|
601
|
+
if (u.content.type === "text") this.queue?.push({ type: "text", text: u.content.text });
|
|
602
|
+
break;
|
|
603
|
+
case "agent_thought_chunk":
|
|
604
|
+
if (u.content.type === "text") this.queue?.push({ type: "thinking", text: u.content.text });
|
|
605
|
+
break;
|
|
606
|
+
case "tool_call": {
|
|
607
|
+
const t = u;
|
|
608
|
+
const meta = { name: t.kind ?? "tool", ...t.title ? { title: t.title } : {}, ...t.kind ? { kind: t.kind } : {} };
|
|
609
|
+
this.toolMeta.set(t.toolCallId, meta);
|
|
610
|
+
this.queue?.push({
|
|
611
|
+
type: "tool_call",
|
|
612
|
+
id: t.toolCallId,
|
|
613
|
+
status: "started",
|
|
614
|
+
...meta,
|
|
615
|
+
...t.rawInput !== void 0 ? { input: t.rawInput } : {}
|
|
616
|
+
});
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
case "tool_call_update": {
|
|
620
|
+
const t = u;
|
|
621
|
+
const known = this.toolMeta.get(t.toolCallId);
|
|
622
|
+
const meta = {
|
|
623
|
+
name: t.kind ?? known?.name ?? "tool",
|
|
624
|
+
...t.title ?? known?.title ? { title: t.title ?? known?.title } : {},
|
|
625
|
+
...t.kind ?? known?.kind ? { kind: t.kind ?? known?.kind } : {}
|
|
626
|
+
};
|
|
627
|
+
this.queue?.push({
|
|
628
|
+
type: "tool_call",
|
|
629
|
+
id: t.toolCallId,
|
|
630
|
+
status: t.status === "completed" ? "completed" : t.status === "failed" ? "failed" : "updated",
|
|
631
|
+
...meta,
|
|
632
|
+
...t.rawOutput !== void 0 ? { output: t.rawOutput } : {}
|
|
633
|
+
});
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
case "usage_update": {
|
|
637
|
+
const cost = u.cost;
|
|
638
|
+
if (cost && typeof cost.amount === "number" && (cost.currency ?? "USD") === "USD") this.costUsd = cost.amount;
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
default:
|
|
642
|
+
this.queue?.push({ type: "raw", provider: this.provider, payload: u });
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async *send(input) {
|
|
647
|
+
if (this.closed) throw new ProviderError(this.provider, "session is closed");
|
|
648
|
+
if (this.queue) throw new ProviderError(this.provider, "a turn is already running");
|
|
649
|
+
await this.ensureOpen();
|
|
650
|
+
const sessionId = this.sessionId;
|
|
651
|
+
const text = typeof input === "string" ? input : input.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n");
|
|
652
|
+
const media = typeof input === "string" ? [] : input.filter((b) => b.type === "image");
|
|
653
|
+
const queue = new AsyncQueue();
|
|
654
|
+
this.queue = queue;
|
|
655
|
+
this.costUsd = void 0;
|
|
656
|
+
this.conn.agent.prompt({ sessionId, prompt: toAcpBlocks(text, media, this.provider) }).then((res) => {
|
|
657
|
+
queue.push({
|
|
658
|
+
type: "done",
|
|
659
|
+
usage: mapAcpUsage(res.usage ?? void 0),
|
|
660
|
+
...this.costUsd !== void 0 ? { costUsd: this.costUsd } : {},
|
|
661
|
+
stopReason: mapStopReason(res.stopReason)
|
|
662
|
+
});
|
|
663
|
+
queue.end();
|
|
664
|
+
}).catch((err) => queue.fail(this.cfg.classify(err)));
|
|
665
|
+
try {
|
|
666
|
+
yield { type: "session", sessionId };
|
|
667
|
+
for await (const event of queue) yield event;
|
|
668
|
+
} finally {
|
|
669
|
+
if (this.queue === queue) this.queue = null;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
async interrupt() {
|
|
673
|
+
if (!this.sessionId || !this.conn) return;
|
|
674
|
+
await this.conn.agent.cancel({ sessionId: this.sessionId }).catch(() => {
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
async close() {
|
|
678
|
+
if (this.closed) return;
|
|
679
|
+
this.closed = true;
|
|
680
|
+
this.queue?.fail(new ProviderError(this.provider, "session closed"));
|
|
681
|
+
this.queue = null;
|
|
682
|
+
this.conn?.close();
|
|
683
|
+
this.conn = void 0;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
498
686
|
function supportsResume(init) {
|
|
499
687
|
const caps = init.agentCapabilities;
|
|
500
688
|
return caps?.sessionCapabilities?.resume !== void 0;
|
|
@@ -787,15 +975,419 @@ function mediaPrompt(text, media) {
|
|
|
787
975
|
}
|
|
788
976
|
|
|
789
977
|
// src/core/providers/codex.ts
|
|
790
|
-
import { spawn as
|
|
978
|
+
import { spawn as spawn4, spawnSync as spawnSync3 } from "child_process";
|
|
791
979
|
import * as fs4 from "fs";
|
|
792
980
|
import * as os4 from "os";
|
|
793
981
|
import * as path4 from "path";
|
|
794
|
-
import * as
|
|
982
|
+
import * as readline4 from "readline";
|
|
795
983
|
|
|
796
|
-
// src/core/providers/
|
|
984
|
+
// src/core/providers/codexSession.ts
|
|
797
985
|
import { spawn as spawn2 } from "child_process";
|
|
798
986
|
import * as readline2 from "readline";
|
|
987
|
+
var CodexAgentSession = class {
|
|
988
|
+
constructor(config) {
|
|
989
|
+
this.config = config;
|
|
990
|
+
}
|
|
991
|
+
config;
|
|
992
|
+
provider = "codex";
|
|
993
|
+
child;
|
|
994
|
+
nextId = 1;
|
|
995
|
+
pending = /* @__PURE__ */ new Map();
|
|
996
|
+
threadId;
|
|
997
|
+
currentTurnId;
|
|
998
|
+
queue = null;
|
|
999
|
+
lastUsage;
|
|
1000
|
+
opening;
|
|
1001
|
+
closed = false;
|
|
1002
|
+
/** Item text already emitted as deltas, so item/completed only fills gaps. */
|
|
1003
|
+
emitted = /* @__PURE__ */ new Map();
|
|
1004
|
+
get id() {
|
|
1005
|
+
return this.threadId;
|
|
1006
|
+
}
|
|
1007
|
+
fail(err) {
|
|
1008
|
+
for (const [, p] of this.pending) p.reject(err);
|
|
1009
|
+
this.pending.clear();
|
|
1010
|
+
this.queue?.fail(err);
|
|
1011
|
+
this.queue = null;
|
|
1012
|
+
}
|
|
1013
|
+
classify(err) {
|
|
1014
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1015
|
+
if (looksLikeAuthFailure(message)) {
|
|
1016
|
+
return new AuthRequiredError("codex", this.config.loginCommand, message.slice(0, 200));
|
|
1017
|
+
}
|
|
1018
|
+
return classifyProviderFailure("codex", this.config.loginCommand, err);
|
|
1019
|
+
}
|
|
1020
|
+
request(method, params) {
|
|
1021
|
+
const child = this.child;
|
|
1022
|
+
if (!child?.stdin?.writable) return Promise.reject(new ProviderError("codex", "app-server is not running"));
|
|
1023
|
+
const id = this.nextId++;
|
|
1024
|
+
const promise = new Promise((resolve, reject) => {
|
|
1025
|
+
this.pending.set(id, { resolve, reject });
|
|
1026
|
+
});
|
|
1027
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
|
|
1028
|
+
`);
|
|
1029
|
+
return promise;
|
|
1030
|
+
}
|
|
1031
|
+
respond(id, result) {
|
|
1032
|
+
this.child?.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}
|
|
1033
|
+
`);
|
|
1034
|
+
}
|
|
1035
|
+
notify(method, params) {
|
|
1036
|
+
this.child?.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
1037
|
+
`);
|
|
1038
|
+
}
|
|
1039
|
+
ensureOpen() {
|
|
1040
|
+
this.opening ??= this.open();
|
|
1041
|
+
return this.opening;
|
|
1042
|
+
}
|
|
1043
|
+
async open() {
|
|
1044
|
+
const { executable, env, options } = this.config;
|
|
1045
|
+
const child = spawn2(executable, ["app-server"], {
|
|
1046
|
+
cwd: options.cwd,
|
|
1047
|
+
env,
|
|
1048
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1049
|
+
});
|
|
1050
|
+
this.child = child;
|
|
1051
|
+
let stderr = "";
|
|
1052
|
+
child.stderr?.on("data", (d) => {
|
|
1053
|
+
stderr += d.toString();
|
|
1054
|
+
if (stderr.length > 16e3) stderr = stderr.slice(-8e3);
|
|
1055
|
+
});
|
|
1056
|
+
child.on("error", (err) => this.fail(this.classify(err)));
|
|
1057
|
+
child.on("exit", (code) => {
|
|
1058
|
+
if (this.closed) return;
|
|
1059
|
+
this.fail(this.classify(new Error(`codex app-server exited with code ${code}${stderr ? `: ${stderr.trim().slice(-400)}` : ""}`)));
|
|
1060
|
+
});
|
|
1061
|
+
const rl = readline2.createInterface({ input: child.stdout });
|
|
1062
|
+
rl.on("line", (line) => {
|
|
1063
|
+
let msg;
|
|
1064
|
+
try {
|
|
1065
|
+
msg = JSON.parse(line);
|
|
1066
|
+
} catch {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
this.dispatch(msg);
|
|
1070
|
+
});
|
|
1071
|
+
await this.request("initialize", {
|
|
1072
|
+
clientInfo: { name: this.config.options.appName ?? "yagami", title: this.config.options.appName ?? "yagami", version: VERSION },
|
|
1073
|
+
capabilities: { experimentalApi: true, requestAttestation: false }
|
|
1074
|
+
});
|
|
1075
|
+
this.notify("initialized", {});
|
|
1076
|
+
const native = options.native ?? {};
|
|
1077
|
+
const overrides = {
|
|
1078
|
+
cwd: options.cwd,
|
|
1079
|
+
...options.model ? { model: options.model } : {},
|
|
1080
|
+
...native.approvalPolicy ? { approvalPolicy: native.approvalPolicy } : {},
|
|
1081
|
+
...native.sandbox ? { sandbox: native.sandbox } : {},
|
|
1082
|
+
...native.config ? { config: native.config } : {},
|
|
1083
|
+
...options.systemPrompt ? { developerInstructions: options.systemPrompt } : {}
|
|
1084
|
+
};
|
|
1085
|
+
if (options.resume) {
|
|
1086
|
+
try {
|
|
1087
|
+
const resumed = await this.request("thread/resume", { threadId: options.resume, ...overrides });
|
|
1088
|
+
this.threadId = resumed["thread"]?.id ?? options.resume;
|
|
1089
|
+
return;
|
|
1090
|
+
} catch {
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const started = await this.request("thread/start", overrides);
|
|
1094
|
+
this.threadId = started["thread"]?.id;
|
|
1095
|
+
if (!this.threadId) throw new ProviderError("codex", "thread/start returned no thread id");
|
|
1096
|
+
}
|
|
1097
|
+
// ── inbound traffic ────────────────────────────────────────────────
|
|
1098
|
+
dispatch(msg) {
|
|
1099
|
+
if (msg.method === void 0 && msg.id !== void 0) {
|
|
1100
|
+
const pending = this.pending.get(msg.id);
|
|
1101
|
+
if (!pending) return;
|
|
1102
|
+
this.pending.delete(msg.id);
|
|
1103
|
+
if (msg.error) pending.reject(this.classify(new Error(msg.error.message ?? "codex error")));
|
|
1104
|
+
else pending.resolve(msg.result ?? {});
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
if (!msg.method) return;
|
|
1108
|
+
if (msg.id !== void 0) {
|
|
1109
|
+
void this.handleServerRequest(msg.method, msg.id, msg.params ?? {});
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
this.handleNotification(msg.method, msg.params ?? {});
|
|
1113
|
+
}
|
|
1114
|
+
push(event) {
|
|
1115
|
+
this.queue?.push(event);
|
|
1116
|
+
}
|
|
1117
|
+
handleNotification(method, params) {
|
|
1118
|
+
if (params["threadId"] !== void 0 && params["threadId"] !== this.threadId) return;
|
|
1119
|
+
switch (method) {
|
|
1120
|
+
case "item/agentMessage/delta": {
|
|
1121
|
+
const itemId = params["itemId"];
|
|
1122
|
+
const delta = params["delta"];
|
|
1123
|
+
this.emitted.set(itemId, (this.emitted.get(itemId) ?? 0) + delta.length);
|
|
1124
|
+
this.push({ type: "text", text: delta });
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
case "item/started":
|
|
1128
|
+
case "item/completed": {
|
|
1129
|
+
this.handleItem(params["item"], method === "item/completed");
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
case "thread/tokenUsage/updated": {
|
|
1133
|
+
const usage = params["tokenUsage"];
|
|
1134
|
+
const last = usage?.last;
|
|
1135
|
+
if (last) {
|
|
1136
|
+
this.lastUsage = {
|
|
1137
|
+
input_tokens: last["inputTokens"] ?? 0,
|
|
1138
|
+
output_tokens: last["outputTokens"] ?? 0,
|
|
1139
|
+
cache_read_input_tokens: last["cachedInputTokens"] ?? 0,
|
|
1140
|
+
cache_creation_input_tokens: last["cacheWriteInputTokens"] ?? 0
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
case "turn/completed": {
|
|
1146
|
+
const turn = params["turn"];
|
|
1147
|
+
const queue = this.queue;
|
|
1148
|
+
this.queue = null;
|
|
1149
|
+
this.currentTurnId = void 0;
|
|
1150
|
+
if (!queue) break;
|
|
1151
|
+
if (turn.status === "failed") {
|
|
1152
|
+
queue.fail(this.classify(new Error(turn.error?.message ?? "turn failed")));
|
|
1153
|
+
} else {
|
|
1154
|
+
queue.push({
|
|
1155
|
+
type: "done",
|
|
1156
|
+
...this.lastUsage ? { usage: this.lastUsage } : {},
|
|
1157
|
+
stopReason: turn.status === "interrupted" ? "interrupted" : "end_turn"
|
|
1158
|
+
});
|
|
1159
|
+
queue.end();
|
|
1160
|
+
}
|
|
1161
|
+
break;
|
|
1162
|
+
}
|
|
1163
|
+
case "error": {
|
|
1164
|
+
this.push({ type: "raw", provider: "codex", payload: { method, params } });
|
|
1165
|
+
break;
|
|
1166
|
+
}
|
|
1167
|
+
default:
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
/** Normalize thread items into tool_call / thinking events. */
|
|
1172
|
+
handleItem(item, completed) {
|
|
1173
|
+
if (!item) return;
|
|
1174
|
+
const id = String(item["id"] ?? "");
|
|
1175
|
+
switch (item["type"]) {
|
|
1176
|
+
case "agentMessage": {
|
|
1177
|
+
if (!completed) break;
|
|
1178
|
+
const text = String(item["text"] ?? "");
|
|
1179
|
+
const seen = this.emitted.get(id) ?? 0;
|
|
1180
|
+
if (text.length > seen) this.push({ type: "text", text: text.slice(seen) });
|
|
1181
|
+
this.emitted.delete(id);
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
case "reasoning": {
|
|
1185
|
+
if (!completed) break;
|
|
1186
|
+
const summary = item["summary"]?.join("\n") ?? "";
|
|
1187
|
+
if (summary) this.push({ type: "thinking", text: summary });
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
case "commandExecution": {
|
|
1191
|
+
const failed = item["status"] === "failed" || item["status"] === "declined";
|
|
1192
|
+
this.push({
|
|
1193
|
+
type: "tool_call",
|
|
1194
|
+
id,
|
|
1195
|
+
name: "shell",
|
|
1196
|
+
status: completed ? failed ? "failed" : "completed" : "started",
|
|
1197
|
+
title: String(item["command"] ?? "command"),
|
|
1198
|
+
kind: "execute",
|
|
1199
|
+
input: { command: item["command"], cwd: item["cwd"] },
|
|
1200
|
+
...completed ? { output: { output: item["aggregatedOutput"], exitCode: item["exitCode"] } } : {}
|
|
1201
|
+
});
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
case "fileChange": {
|
|
1205
|
+
const changes = item["changes"] ?? [];
|
|
1206
|
+
const failed = item["status"] === "failed" || item["status"] === "declined";
|
|
1207
|
+
this.push({
|
|
1208
|
+
type: "tool_call",
|
|
1209
|
+
id,
|
|
1210
|
+
name: "apply_patch",
|
|
1211
|
+
status: completed ? failed ? "failed" : "completed" : "started",
|
|
1212
|
+
title: changes.map((c) => c.path).filter(Boolean).join(", ") || "file changes",
|
|
1213
|
+
kind: "edit",
|
|
1214
|
+
input: { changes }
|
|
1215
|
+
});
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
case "mcpToolCall": {
|
|
1219
|
+
this.push({
|
|
1220
|
+
type: "tool_call",
|
|
1221
|
+
id,
|
|
1222
|
+
name: `${String(item["server"] ?? "mcp")}.${String(item["tool"] ?? "tool")}`,
|
|
1223
|
+
status: completed ? item["status"] === "failed" ? "failed" : "completed" : "started",
|
|
1224
|
+
kind: "other",
|
|
1225
|
+
input: item["arguments"]
|
|
1226
|
+
});
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
case "webSearch": {
|
|
1230
|
+
this.push({
|
|
1231
|
+
type: "tool_call",
|
|
1232
|
+
id,
|
|
1233
|
+
name: "web_search",
|
|
1234
|
+
status: completed ? "completed" : "started",
|
|
1235
|
+
kind: "fetch",
|
|
1236
|
+
input: { query: item["query"] }
|
|
1237
|
+
});
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
case "userMessage":
|
|
1241
|
+
case "plan":
|
|
1242
|
+
break;
|
|
1243
|
+
default:
|
|
1244
|
+
this.push({ type: "raw", provider: "codex", payload: item });
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
// ── approvals: forwarded to the host, answered like the TUI would ──
|
|
1249
|
+
async decide(request) {
|
|
1250
|
+
try {
|
|
1251
|
+
const decision = await this.config.options.permissions.decide(request);
|
|
1252
|
+
this.push({ type: "permission", request, decision });
|
|
1253
|
+
return decision;
|
|
1254
|
+
} catch {
|
|
1255
|
+
return "deny";
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
async handleServerRequest(method, id, params) {
|
|
1259
|
+
switch (method) {
|
|
1260
|
+
case "item/commandExecution/requestApproval": {
|
|
1261
|
+
const decision = await this.decide({
|
|
1262
|
+
provider: "codex",
|
|
1263
|
+
...this.threadId ? { sessionId: this.threadId } : {},
|
|
1264
|
+
tool: "shell",
|
|
1265
|
+
kind: "execute",
|
|
1266
|
+
title: String(params["command"] ?? "command"),
|
|
1267
|
+
input: { command: params["command"], cwd: params["cwd"], reason: params["reason"] },
|
|
1268
|
+
raw: params
|
|
1269
|
+
});
|
|
1270
|
+
this.respond(id, {
|
|
1271
|
+
decision: decision === "allow" ? "accept" : decision === "allow_always" ? "acceptForSession" : "decline"
|
|
1272
|
+
});
|
|
1273
|
+
break;
|
|
1274
|
+
}
|
|
1275
|
+
case "item/fileChange/requestApproval": {
|
|
1276
|
+
const decision = await this.decide({
|
|
1277
|
+
provider: "codex",
|
|
1278
|
+
...this.threadId ? { sessionId: this.threadId } : {},
|
|
1279
|
+
tool: "apply_patch",
|
|
1280
|
+
kind: "edit",
|
|
1281
|
+
title: String(params["reason"] ?? "apply file changes"),
|
|
1282
|
+
input: { reason: params["reason"], grantRoot: params["grantRoot"] },
|
|
1283
|
+
raw: params
|
|
1284
|
+
});
|
|
1285
|
+
this.respond(id, {
|
|
1286
|
+
decision: decision === "allow" ? "accept" : decision === "allow_always" ? "acceptForSession" : "decline"
|
|
1287
|
+
});
|
|
1288
|
+
break;
|
|
1289
|
+
}
|
|
1290
|
+
case "item/permissions/requestApproval": {
|
|
1291
|
+
const requested = params["permissions"];
|
|
1292
|
+
const decision = await this.decide({
|
|
1293
|
+
provider: "codex",
|
|
1294
|
+
...this.threadId ? { sessionId: this.threadId } : {},
|
|
1295
|
+
tool: "permissions",
|
|
1296
|
+
kind: "other",
|
|
1297
|
+
title: String(params["reason"] ?? "extra permissions"),
|
|
1298
|
+
input: requested,
|
|
1299
|
+
raw: params
|
|
1300
|
+
});
|
|
1301
|
+
const granted = decision === "allow" || decision === "allow_always";
|
|
1302
|
+
this.respond(id, {
|
|
1303
|
+
permissions: granted ? { network: requested?.["network"] ?? void 0, fileSystem: requested?.["fileSystem"] ?? void 0 } : {},
|
|
1304
|
+
scope: decision === "allow_always" ? "session" : "turn"
|
|
1305
|
+
});
|
|
1306
|
+
break;
|
|
1307
|
+
}
|
|
1308
|
+
// legacy approval shapes, still sent by some codepaths
|
|
1309
|
+
case "execCommandApproval":
|
|
1310
|
+
case "applyPatchApproval": {
|
|
1311
|
+
const decision = await this.decide({
|
|
1312
|
+
provider: "codex",
|
|
1313
|
+
...this.threadId ? { sessionId: this.threadId } : {},
|
|
1314
|
+
tool: method === "execCommandApproval" ? "shell" : "apply_patch",
|
|
1315
|
+
kind: method === "execCommandApproval" ? "execute" : "edit",
|
|
1316
|
+
title: String(params["command"] ?? params["reason"] ?? "approval"),
|
|
1317
|
+
input: params,
|
|
1318
|
+
raw: params
|
|
1319
|
+
});
|
|
1320
|
+
this.respond(id, {
|
|
1321
|
+
decision: decision === "allow" ? "approved" : decision === "allow_always" ? "approved_for_session" : { denied: { rejection: "denied by the user" } }
|
|
1322
|
+
});
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
case "item/tool/requestUserInput": {
|
|
1326
|
+
this.respond(id, { answers: {} });
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
case "mcpServer/elicitation/request": {
|
|
1330
|
+
this.respond(id, { action: "decline", content: null, _meta: null });
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1333
|
+
default: {
|
|
1334
|
+
this.child?.stdin?.write(
|
|
1335
|
+
`${JSON.stringify({ jsonrpc: "2.0", id, error: { code: -32601, message: `unsupported request: ${method}` } })}
|
|
1336
|
+
`
|
|
1337
|
+
);
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
// ── the ProviderSession surface ────────────────────────────────────
|
|
1343
|
+
async *send(input) {
|
|
1344
|
+
if (this.closed) throw new ProviderError("codex", "session is closed");
|
|
1345
|
+
if (this.queue) throw new ProviderError("codex", "a turn is already running");
|
|
1346
|
+
await this.ensureOpen();
|
|
1347
|
+
const blocks = typeof input === "string" ? [{ type: "text", text: input }] : input;
|
|
1348
|
+
const { paths, cleanup } = writeTempImages(blocks);
|
|
1349
|
+
const items = [];
|
|
1350
|
+
for (const block of blocks) {
|
|
1351
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
1352
|
+
items.push({ type: "text", text: block.text, text_elements: [] });
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
for (const p of paths) items.push({ type: "localImage", path: p });
|
|
1356
|
+
if (items.length === 0) items.push({ type: "text", text: "", text_elements: [] });
|
|
1357
|
+
const queue = new AsyncQueue();
|
|
1358
|
+
this.queue = queue;
|
|
1359
|
+
this.lastUsage = void 0;
|
|
1360
|
+
try {
|
|
1361
|
+
const result = await this.request("turn/start", {
|
|
1362
|
+
threadId: this.threadId,
|
|
1363
|
+
input: items,
|
|
1364
|
+
...this.config.options.effort ? { effort: this.config.options.effort } : {}
|
|
1365
|
+
});
|
|
1366
|
+
this.currentTurnId = result["turn"]?.id;
|
|
1367
|
+
yield { type: "session", sessionId: this.threadId };
|
|
1368
|
+
for await (const event of queue) yield event;
|
|
1369
|
+
} finally {
|
|
1370
|
+
cleanup();
|
|
1371
|
+
if (this.queue === queue) this.queue = null;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
async interrupt() {
|
|
1375
|
+
if (!this.threadId || !this.currentTurnId) return;
|
|
1376
|
+
await this.request("turn/interrupt", { threadId: this.threadId, turnId: this.currentTurnId }).catch(() => {
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
async close() {
|
|
1380
|
+
if (this.closed) return;
|
|
1381
|
+
this.closed = true;
|
|
1382
|
+
this.fail(new ProviderError("codex", "session closed"));
|
|
1383
|
+
this.child?.kill("SIGTERM");
|
|
1384
|
+
this.child = void 0;
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
// src/core/providers/jsonl.ts
|
|
1389
|
+
import { spawn as spawn3 } from "child_process";
|
|
1390
|
+
import * as readline3 from "readline";
|
|
799
1391
|
var ProcessExitError = class extends Error {
|
|
800
1392
|
constructor(exitCode, stderr) {
|
|
801
1393
|
super(`process exited with code ${exitCode}${stderr ? `: ${stderr.trim().split("\n").slice(-3).join(" | ")}` : ""}`);
|
|
@@ -808,7 +1400,7 @@ var ProcessExitError = class extends Error {
|
|
|
808
1400
|
};
|
|
809
1401
|
function spawnJsonl(options) {
|
|
810
1402
|
const queue = new AsyncQueue();
|
|
811
|
-
const child =
|
|
1403
|
+
const child = spawn3(options.command, options.args, {
|
|
812
1404
|
cwd: options.cwd,
|
|
813
1405
|
env: options.env,
|
|
814
1406
|
stdio: [options.stdin !== void 0 ? "pipe" : "ignore", "pipe", "pipe"]
|
|
@@ -818,7 +1410,7 @@ function spawnJsonl(options) {
|
|
|
818
1410
|
stderr += chunk.toString();
|
|
819
1411
|
if (stderr.length > 16e3) stderr = stderr.slice(-8e3);
|
|
820
1412
|
});
|
|
821
|
-
const rl =
|
|
1413
|
+
const rl = readline3.createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
822
1414
|
rl.on("line", (line) => {
|
|
823
1415
|
const trimmed = line.trim();
|
|
824
1416
|
if (!trimmed.startsWith("{")) return;
|
|
@@ -933,10 +1525,24 @@ var CodexProvider = class {
|
|
|
933
1525
|
if (req.signal?.aborted) return;
|
|
934
1526
|
if (!done) throw new ProviderError(this.id, "codex exited without completing the turn");
|
|
935
1527
|
}
|
|
1528
|
+
/**
|
|
1529
|
+
* A live, interactive Codex session (`codex app-server` — the TUI's own
|
|
1530
|
+
* engine): streamed tool events, the user's config.toml verbatim, and
|
|
1531
|
+
* approval requests forwarded to the host's permission handler. The
|
|
1532
|
+
* completion-turn `run()` path above stays for API-style callers.
|
|
1533
|
+
*/
|
|
1534
|
+
openSession(options) {
|
|
1535
|
+
return new CodexAgentSession({
|
|
1536
|
+
executable: this.executable,
|
|
1537
|
+
env: this.env,
|
|
1538
|
+
loginCommand: this.loginCommand,
|
|
1539
|
+
options
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
936
1542
|
/** Ask the app-server protocol for the model catalog (no tokens spent). */
|
|
937
1543
|
listModels() {
|
|
938
1544
|
return new Promise((resolve, reject) => {
|
|
939
|
-
const child =
|
|
1545
|
+
const child = spawn4(this.executable, ["app-server"], { env: this.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
940
1546
|
let stderr = "";
|
|
941
1547
|
let settled = false;
|
|
942
1548
|
const finish = (fn) => {
|
|
@@ -954,7 +1560,7 @@ var CodexProvider = class {
|
|
|
954
1560
|
"close",
|
|
955
1561
|
() => finish(() => reject(classifyProviderFailure(this.id, this.loginCommand, new Error(stderr.trim() || "app-server exited"))))
|
|
956
1562
|
);
|
|
957
|
-
const rl =
|
|
1563
|
+
const rl = readline4.createInterface({ input: child.stdout });
|
|
958
1564
|
rl.on("line", (line) => {
|
|
959
1565
|
let msg;
|
|
960
1566
|
try {
|
|
@@ -1115,13 +1721,13 @@ function detectProviders(config = {}) {
|
|
|
1115
1721
|
const preset = presetFor(id);
|
|
1116
1722
|
const entry = config[id] ?? {};
|
|
1117
1723
|
const command = entry.command ?? preset?.command ?? id;
|
|
1118
|
-
const
|
|
1724
|
+
const path8 = findExecutable(command, entry.path ? { explicit: entry.path } : {});
|
|
1119
1725
|
return {
|
|
1120
1726
|
id,
|
|
1121
1727
|
label: entry.label ?? preset?.label ?? id,
|
|
1122
1728
|
kind: preset?.kind ?? "acp",
|
|
1123
|
-
installed:
|
|
1124
|
-
...
|
|
1729
|
+
installed: path8 !== void 0 && entry.enabled !== false,
|
|
1730
|
+
...path8 ? { path: path8 } : {},
|
|
1125
1731
|
loginCommand: entry.loginCommand ?? preset?.loginCommand ?? `${command} (sign in per its docs)`,
|
|
1126
1732
|
installHint: preset?.installHint ?? `Install \`${command}\`.`
|
|
1127
1733
|
};
|
|
@@ -1792,6 +2398,266 @@ ${promptText}`;
|
|
|
1792
2398
|
}
|
|
1793
2399
|
};
|
|
1794
2400
|
|
|
2401
|
+
// src/core/hostConfig.ts
|
|
2402
|
+
import * as fs7 from "fs";
|
|
2403
|
+
import * as os6 from "os";
|
|
2404
|
+
import * as path7 from "path";
|
|
2405
|
+
function yagamiConfigDir() {
|
|
2406
|
+
return process.env["YAGAMI_CONFIG_DIR"] ?? path7.join(os6.homedir(), ".config", "yagami");
|
|
2407
|
+
}
|
|
2408
|
+
function loadHostEngineConfig() {
|
|
2409
|
+
let file = {};
|
|
2410
|
+
try {
|
|
2411
|
+
file = JSON.parse(fs7.readFileSync(path7.join(yagamiConfigDir(), "config.json"), "utf8"));
|
|
2412
|
+
} catch {
|
|
2413
|
+
}
|
|
2414
|
+
const env = process.env;
|
|
2415
|
+
const defaultProvider = env["YAGAMI_PROVIDER"] ?? (typeof file["defaultProvider"] === "string" ? file["defaultProvider"] : void 0);
|
|
2416
|
+
const defaultModel = env["YAGAMI_DEFAULT_MODEL"] ?? (typeof file["defaultModel"] === "string" ? file["defaultModel"] : void 0);
|
|
2417
|
+
const claudePath = env["YAGAMI_CLAUDE_PATH"] ?? (typeof file["claudePath"] === "string" ? file["claudePath"] : void 0);
|
|
2418
|
+
const claudeConfigDir = typeof file["claudeConfigDir"] === "string" ? file["claudeConfigDir"] : void 0;
|
|
2419
|
+
const providers = file["providers"];
|
|
2420
|
+
return {
|
|
2421
|
+
...defaultProvider ? { defaultProvider } : {},
|
|
2422
|
+
...defaultModel ? { defaultModel } : {},
|
|
2423
|
+
...providers != null && typeof providers === "object" && !Array.isArray(providers) ? { providerConfig: providers } : {},
|
|
2424
|
+
...claudePath ? { claudePath } : {},
|
|
2425
|
+
...claudeConfigDir ? { claudeConfigDir } : {}
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
// src/core/openai.ts
|
|
2430
|
+
var IGNORED_OPENAI_PARAMS = [
|
|
2431
|
+
"presence_penalty",
|
|
2432
|
+
"frequency_penalty",
|
|
2433
|
+
"logit_bias",
|
|
2434
|
+
"logprobs",
|
|
2435
|
+
"top_logprobs",
|
|
2436
|
+
"seed",
|
|
2437
|
+
"user",
|
|
2438
|
+
"response_format",
|
|
2439
|
+
"prediction",
|
|
2440
|
+
"modalities",
|
|
2441
|
+
"audio",
|
|
2442
|
+
"store",
|
|
2443
|
+
"parallel_tool_calls",
|
|
2444
|
+
"web_search_options"
|
|
2445
|
+
];
|
|
2446
|
+
var EFFORT_MAP = {
|
|
2447
|
+
minimal: "low",
|
|
2448
|
+
low: "low",
|
|
2449
|
+
medium: "medium",
|
|
2450
|
+
high: "high",
|
|
2451
|
+
xhigh: "xhigh",
|
|
2452
|
+
max: "max"
|
|
2453
|
+
};
|
|
2454
|
+
function imagePartToBlock(part) {
|
|
2455
|
+
const url = part["image_url"]?.["url"];
|
|
2456
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
2457
|
+
throw new ApiError(400, "invalid_request_error", "`image_url` parts must carry an `image_url.url` string");
|
|
2458
|
+
}
|
|
2459
|
+
const dataUrl = /^data:([^;,]+);base64,(.+)$/s.exec(url);
|
|
2460
|
+
if (dataUrl) {
|
|
2461
|
+
return { type: "image", source: { type: "base64", media_type: dataUrl[1], data: dataUrl[2] } };
|
|
2462
|
+
}
|
|
2463
|
+
if (/^https?:\/\//.test(url)) {
|
|
2464
|
+
return { type: "image", source: { type: "url", url } };
|
|
2465
|
+
}
|
|
2466
|
+
throw new ApiError(400, "invalid_request_error", "`image_url.url` must be a data: URL or an http(s) URL");
|
|
2467
|
+
}
|
|
2468
|
+
function partsToContent(parts, role) {
|
|
2469
|
+
const blocks = [];
|
|
2470
|
+
for (const part of parts) {
|
|
2471
|
+
if (part?.type === "text" && typeof part["text"] === "string") {
|
|
2472
|
+
blocks.push({ type: "text", text: part["text"] });
|
|
2473
|
+
} else if (part?.type === "image_url" && role === "user") {
|
|
2474
|
+
blocks.push(imagePartToBlock(part));
|
|
2475
|
+
} else {
|
|
2476
|
+
throw new ApiError(
|
|
2477
|
+
400,
|
|
2478
|
+
"invalid_request_error",
|
|
2479
|
+
`unsupported content part type "${String(part?.type)}" for role "${role}" (yagami supports "text", plus "image_url" in user messages)`
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
return blocks.every((b) => b.type === "text") ? blocks.map((b) => b["text"]).join("\n") : blocks;
|
|
2484
|
+
}
|
|
2485
|
+
function messageText(content, role) {
|
|
2486
|
+
if (content == null) return "";
|
|
2487
|
+
if (typeof content === "string") return content;
|
|
2488
|
+
const flattened = partsToContent(content, role);
|
|
2489
|
+
if (typeof flattened !== "string") {
|
|
2490
|
+
throw new ApiError(400, "invalid_request_error", `"${role}" messages may only contain text parts`);
|
|
2491
|
+
}
|
|
2492
|
+
return flattened;
|
|
2493
|
+
}
|
|
2494
|
+
function chatToMessagesRequest(body) {
|
|
2495
|
+
if (body == null || typeof body !== "object") {
|
|
2496
|
+
throw new ApiError(400, "invalid_request_error", "request body must be a JSON object");
|
|
2497
|
+
}
|
|
2498
|
+
if (body.tools != null || body.tool_choice != null || body.functions != null || body.function_call != null) {
|
|
2499
|
+
throw new ApiError(
|
|
2500
|
+
400,
|
|
2501
|
+
"invalid_request_error",
|
|
2502
|
+
"yagami does not support `tools`/function calling: the backing engine runs as a pure completions endpoint and never executes or emits tool calls."
|
|
2503
|
+
);
|
|
2504
|
+
}
|
|
2505
|
+
if (body.n != null && body.n !== 1) {
|
|
2506
|
+
throw new ApiError(400, "invalid_request_error", "`n` must be 1 (yagami produces a single completion)");
|
|
2507
|
+
}
|
|
2508
|
+
if (!Array.isArray(body.messages) || body.messages.length === 0) {
|
|
2509
|
+
throw new ApiError(400, "invalid_request_error", "`messages` must be a non-empty array");
|
|
2510
|
+
}
|
|
2511
|
+
const systemParts = [];
|
|
2512
|
+
const messages = [];
|
|
2513
|
+
for (const [i, m] of body.messages.entries()) {
|
|
2514
|
+
const role = m?.role;
|
|
2515
|
+
if (role === "system" || role === "developer") {
|
|
2516
|
+
systemParts.push(messageText(m.content, role));
|
|
2517
|
+
} else if (role === "user") {
|
|
2518
|
+
messages.push({ role: "user", content: Array.isArray(m.content) ? partsToContent(m.content, "user") : m.content ?? "" });
|
|
2519
|
+
} else if (role === "assistant") {
|
|
2520
|
+
messages.push({ role: "assistant", content: messageText(m.content, "assistant") });
|
|
2521
|
+
} else if (role === "tool" || role === "function") {
|
|
2522
|
+
throw new ApiError(400, "invalid_request_error", "yagami does not support tool/function messages (tool calling is disabled by design)");
|
|
2523
|
+
} else {
|
|
2524
|
+
throw new ApiError(400, "invalid_request_error", `messages[${i}].role must be "system", "developer", "user", or "assistant"`);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
const extraIgnored = IGNORED_OPENAI_PARAMS.filter((p) => body[p] != null).map(String);
|
|
2528
|
+
let effort;
|
|
2529
|
+
if (body.reasoning_effort != null) {
|
|
2530
|
+
effort = EFFORT_MAP[String(body.reasoning_effort)];
|
|
2531
|
+
if (!effort) extraIgnored.push("reasoning_effort");
|
|
2532
|
+
}
|
|
2533
|
+
const maxTokens = body.max_completion_tokens ?? body.max_tokens;
|
|
2534
|
+
const system = systemParts.filter((s) => s.length > 0).join("\n\n");
|
|
2535
|
+
const req = {
|
|
2536
|
+
...body.model !== void 0 ? { model: body.model } : {},
|
|
2537
|
+
messages,
|
|
2538
|
+
...system.length > 0 ? { system } : {},
|
|
2539
|
+
...maxTokens !== void 0 ? { max_tokens: maxTokens } : {},
|
|
2540
|
+
...body.temperature !== void 0 ? { temperature: body.temperature } : {},
|
|
2541
|
+
...body.top_p !== void 0 ? { top_p: body.top_p } : {},
|
|
2542
|
+
...body.stop != null ? { stop_sequences: Array.isArray(body.stop) ? body.stop : [body.stop] } : {},
|
|
2543
|
+
...body.metadata != null ? { metadata: body.metadata } : {},
|
|
2544
|
+
...body.service_tier != null ? { service_tier: String(body.service_tier) } : {},
|
|
2545
|
+
...effort ? { effort } : {},
|
|
2546
|
+
...body.stream === true ? { stream: true } : {}
|
|
2547
|
+
};
|
|
2548
|
+
return { req, extraIgnored, includeUsage: body.stream_options?.include_usage === true };
|
|
2549
|
+
}
|
|
2550
|
+
function finishReason(stopReason) {
|
|
2551
|
+
return stopReason === "max_tokens" ? "length" : "stop";
|
|
2552
|
+
}
|
|
2553
|
+
function toOpenAiUsage(usage) {
|
|
2554
|
+
return {
|
|
2555
|
+
prompt_tokens: usage.input_tokens,
|
|
2556
|
+
completion_tokens: usage.output_tokens,
|
|
2557
|
+
total_tokens: usage.input_tokens + usage.output_tokens
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
function toChatCompletion(resp) {
|
|
2561
|
+
const text = resp.content.filter((b) => b.type === "text").map((b) => String(b["text"] ?? "")).join("");
|
|
2562
|
+
const thinking = resp.content.filter((b) => b.type === "thinking").map((b) => String(b["thinking"] ?? "")).join("");
|
|
2563
|
+
return {
|
|
2564
|
+
id: resp.id.replace(/^msg_/, "chatcmpl_"),
|
|
2565
|
+
object: "chat.completion",
|
|
2566
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2567
|
+
model: resp.model,
|
|
2568
|
+
choices: [
|
|
2569
|
+
{
|
|
2570
|
+
index: 0,
|
|
2571
|
+
message: { role: "assistant", content: text, ...thinking ? { reasoning_content: thinking } : {}, refusal: null },
|
|
2572
|
+
finish_reason: finishReason(resp.stop_reason),
|
|
2573
|
+
logprobs: null
|
|
2574
|
+
}
|
|
2575
|
+
],
|
|
2576
|
+
usage: toOpenAiUsage(resp.usage)
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
function openAiErrorBody(err) {
|
|
2580
|
+
return { error: { message: err.message, type: err.type, param: null, code: null } };
|
|
2581
|
+
}
|
|
2582
|
+
var ChatChunkTranslator = class {
|
|
2583
|
+
constructor(includeUsage) {
|
|
2584
|
+
this.includeUsage = includeUsage;
|
|
2585
|
+
}
|
|
2586
|
+
includeUsage;
|
|
2587
|
+
id = "chatcmpl_stream";
|
|
2588
|
+
model = "";
|
|
2589
|
+
created = Math.floor(Date.now() / 1e3);
|
|
2590
|
+
stopReason = null;
|
|
2591
|
+
usage = { input_tokens: 0, output_tokens: 0 };
|
|
2592
|
+
/** Set when the engine reported an error mid-stream (no [DONE] after). */
|
|
2593
|
+
errored = false;
|
|
2594
|
+
chunk(delta, finish = null) {
|
|
2595
|
+
return {
|
|
2596
|
+
id: this.id,
|
|
2597
|
+
object: "chat.completion.chunk",
|
|
2598
|
+
created: this.created,
|
|
2599
|
+
model: this.model,
|
|
2600
|
+
choices: [{ index: 0, delta, finish_reason: finish }]
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
/** Translate one engine SSE event into zero or more OpenAI chunk payloads. */
|
|
2604
|
+
push(ev) {
|
|
2605
|
+
const data = ev.data;
|
|
2606
|
+
switch (ev.event) {
|
|
2607
|
+
case "message_start": {
|
|
2608
|
+
const message = data["message"];
|
|
2609
|
+
if (message?.id) this.id = message.id.replace(/^msg_/, "chatcmpl_");
|
|
2610
|
+
if (message?.model) this.model = message.model;
|
|
2611
|
+
return [this.chunk({ role: "assistant", content: "" })];
|
|
2612
|
+
}
|
|
2613
|
+
case "content_block_delta": {
|
|
2614
|
+
const delta = data["delta"];
|
|
2615
|
+
if (delta?.type === "text_delta" && delta.text) return [this.chunk({ content: delta.text })];
|
|
2616
|
+
if (delta?.type === "thinking_delta" && delta.thinking) return [this.chunk({ reasoning_content: delta.thinking })];
|
|
2617
|
+
return [];
|
|
2618
|
+
}
|
|
2619
|
+
case "message_delta": {
|
|
2620
|
+
const delta = data["delta"];
|
|
2621
|
+
if (delta?.stop_reason) this.stopReason = delta.stop_reason;
|
|
2622
|
+
const usage = data["usage"];
|
|
2623
|
+
if (usage) this.usage = usage;
|
|
2624
|
+
return [];
|
|
2625
|
+
}
|
|
2626
|
+
case "message_stop": {
|
|
2627
|
+
const out = [this.chunk({}, finishReason(this.stopReason))];
|
|
2628
|
+
if (this.includeUsage) {
|
|
2629
|
+
out.push({
|
|
2630
|
+
id: this.id,
|
|
2631
|
+
object: "chat.completion.chunk",
|
|
2632
|
+
created: this.created,
|
|
2633
|
+
model: this.model,
|
|
2634
|
+
choices: [],
|
|
2635
|
+
usage: toOpenAiUsage(this.usage)
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
return out;
|
|
2639
|
+
}
|
|
2640
|
+
case "error": {
|
|
2641
|
+
this.errored = true;
|
|
2642
|
+
const error = data["error"];
|
|
2643
|
+
return [{ error: { message: error?.message ?? "stream error", type: error?.type ?? "api_error", param: null, code: null } }];
|
|
2644
|
+
}
|
|
2645
|
+
default:
|
|
2646
|
+
return [];
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
};
|
|
2650
|
+
function modelListBody(models) {
|
|
2651
|
+
const created = Math.floor(Date.now() / 1e3);
|
|
2652
|
+
return {
|
|
2653
|
+
object: "list",
|
|
2654
|
+
data: models.map((m) => ({ type: "model", object: "model", created, owned_by: "yagami", ...m })),
|
|
2655
|
+
has_more: false,
|
|
2656
|
+
...models[0] ? { first_id: models[0].id } : {},
|
|
2657
|
+
...models.length > 0 ? { last_id: models[models.length - 1].id } : {}
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
|
|
1795
2661
|
export {
|
|
1796
2662
|
ApiError,
|
|
1797
2663
|
YagamiError,
|
|
@@ -1802,6 +2668,7 @@ export {
|
|
|
1802
2668
|
toApiError,
|
|
1803
2669
|
parseModelRef,
|
|
1804
2670
|
qualifiedModel,
|
|
2671
|
+
isSessionProvider,
|
|
1805
2672
|
findExecutable,
|
|
1806
2673
|
resolveExecutable,
|
|
1807
2674
|
resolveClaudeExecutable,
|
|
@@ -1816,6 +2683,13 @@ export {
|
|
|
1816
2683
|
loadProviders,
|
|
1817
2684
|
detectProviders,
|
|
1818
2685
|
SessionCache,
|
|
1819
|
-
YagamiEngine
|
|
2686
|
+
YagamiEngine,
|
|
2687
|
+
yagamiConfigDir,
|
|
2688
|
+
loadHostEngineConfig,
|
|
2689
|
+
chatToMessagesRequest,
|
|
2690
|
+
toChatCompletion,
|
|
2691
|
+
openAiErrorBody,
|
|
2692
|
+
ChatChunkTranslator,
|
|
2693
|
+
modelListBody
|
|
1820
2694
|
};
|
|
1821
|
-
//# sourceMappingURL=chunk-
|
|
2695
|
+
//# sourceMappingURL=chunk-ZYHC7PXX.js.map
|