pi-coralbricks-provider 1.0.28 → 1.0.30
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 +33 -6
- package/index.ts +60 -4
- package/package.json +2 -1
- package/park.ts +122 -0
- package/settings.ts +37 -16
package/README.md
CHANGED
|
@@ -108,7 +108,8 @@ Thinking levels attach to the model id with `:<level>` — e.g. `:low`, `:high`,
|
|
|
108
108
|
|
|
109
109
|
## Provider Settings
|
|
110
110
|
|
|
111
|
-
Run `/coralbricks-settings` to choose the **API surface
|
|
111
|
+
Run `/coralbricks-settings` to choose the **API surface** and, for Responses,
|
|
112
|
+
whether to **park turns**. The searchable,
|
|
112
113
|
bordered settings list matches pi's settings UI; GUI/RPC clients get a selection
|
|
113
114
|
dialog. Changes are saved immediately and apply to the next request without a
|
|
114
115
|
restart, including when a CoralBricks model is already selected.
|
|
@@ -117,7 +118,7 @@ Settings live at `~/.pi/agent/extensions/coralbricks.json` (or under
|
|
|
117
118
|
`PI_CODING_AGENT_DIR` when set):
|
|
118
119
|
|
|
119
120
|
```json
|
|
120
|
-
{ "api": "responses" }
|
|
121
|
+
{ "api": "responses", "park": false }
|
|
121
122
|
```
|
|
122
123
|
|
|
123
124
|
- **`chat-completions`** (default): uses `/v1/chat/completions`; existing behavior
|
|
@@ -126,10 +127,36 @@ Settings live at `~/.pi/agent/extensions/coralbricks.json` (or under
|
|
|
126
127
|
tool-call, image, usage, and cancellation handling. The same model IDs and API
|
|
127
128
|
key work on both surfaces. Thinking levels map to `reasoning.effort` here.
|
|
128
129
|
|
|
129
|
-
Responses requests replay the full local conversation and send
|
|
130
|
-
This integration does
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
By default, Responses requests replay the full local conversation and send
|
|
131
|
+
`store: false`. This integration does not use background jobs or resumable
|
|
132
|
+
streams. Coral's broader Responses API is documented at
|
|
133
|
+
[coralbricks.ai/docs](https://www.coralbricks.ai/docs).
|
|
134
|
+
|
|
135
|
+
### Parking (`"park": true`)
|
|
136
|
+
|
|
137
|
+
With parking on, every Responses request is sent with `store: true` and the
|
|
138
|
+
next request continues it with `previous_response_id`. pi still builds the full
|
|
139
|
+
transcript, but only the items after the parked response (tool results, the
|
|
140
|
+
next user message) go over the wire. Tools are re-sent every time because Coral
|
|
141
|
+
does not carry them across a chain.
|
|
142
|
+
|
|
143
|
+
A turn is chained only when the full input starts with exactly what was sent
|
|
144
|
+
for the parked response, followed by that response's own output. Compaction, a
|
|
145
|
+
model switch, an edited or branched history, or a pi restart falls back to a
|
|
146
|
+
full replay, and that request is parked again. If Coral no longer has the parent
|
|
147
|
+
(`previous_response_not_found`), the same request is re-sent once in full.
|
|
148
|
+
Parked ids live in memory only.
|
|
149
|
+
|
|
150
|
+
Things to know before turning it on:
|
|
151
|
+
|
|
152
|
+
- Coral keeps every parked response on its side. Deleting a parent breaks the
|
|
153
|
+
chain, so the extension does not delete them.
|
|
154
|
+
- Coral bills a chained request as the tool definitions plus the new items. The
|
|
155
|
+
first chained request in a session writes the tool block to cache once, and
|
|
156
|
+
later ones read it back. With a warm cache, a 7-request tool loop cost about
|
|
157
|
+
the same as a full replay on DeepSeek V4.1 Flash and about a third more on
|
|
158
|
+
GLM 5.3 Flash. The parked history itself is not billed again, which matters
|
|
159
|
+
when Coral evicts a long session's cache.
|
|
133
160
|
|
|
134
161
|
Live probes verified text, automatic tool calls, tool-result replay, and switching
|
|
135
162
|
back to Chat Completions on all current models. The gateway currently rejects
|
package/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CoralBricks Provider Extension
|
|
3
3
|
*
|
|
4
|
-
* Registers CoralBricks with Chat Completions (default) or opt-in Responses
|
|
4
|
+
* Registers CoralBricks with Chat Completions (default) or opt-in Responses,
|
|
5
|
+
* optionally parking Responses turns with previous_response_id (park.ts).
|
|
5
6
|
* Base URL: https://inference.coralbricks.ai/v1
|
|
6
7
|
*
|
|
7
8
|
* Model resolution strategy: Stale-While-Revalidate
|
|
@@ -33,6 +34,7 @@ import { getAgentDir, type ExtensionAPI, type ModelRegistry } from "@earendil-wo
|
|
|
33
34
|
import type { AssistantMessageEventStream, SimpleStreamOptions } from "@earendil-works/pi-ai/compat";
|
|
34
35
|
import { clampThinkingLevel, streamOpenAICompletions, streamOpenAIResponses } from "@earendil-works/pi-ai/compat";
|
|
35
36
|
import { loadConfig, registerSettingsCommand } from "./settings";
|
|
37
|
+
import { park, planPark, withParkFallback, type ParkPlan } from "./park";
|
|
36
38
|
import modelsData from "./models.json" with { type: "json" };
|
|
37
39
|
import customModelsData from "./custom-models.json" with { type: "json" };
|
|
38
40
|
import patchData from "./patch.json" with { type: "json" };
|
|
@@ -449,10 +451,18 @@ async function revalidateModels(apiKey: string | undefined, embeddedModels: Json
|
|
|
449
451
|
return merged;
|
|
450
452
|
}
|
|
451
453
|
|
|
454
|
+
/** pi-ai's clampOpenAIPromptCacheKey, inlined: Coral accepts the same field. */
|
|
455
|
+
function clampPromptCacheKey(key: string | undefined): string | undefined {
|
|
456
|
+
if (key === undefined) return undefined;
|
|
457
|
+
const chars = Array.from(key);
|
|
458
|
+
return chars.length <= 64 ? key : chars.slice(0, 64).join("");
|
|
459
|
+
}
|
|
460
|
+
|
|
452
461
|
export function streamCoral(
|
|
453
462
|
model: any,
|
|
454
463
|
context: any,
|
|
455
464
|
options?: SimpleStreamOptions,
|
|
465
|
+
settings: { park?: boolean } = {},
|
|
456
466
|
): AssistantMessageEventStream {
|
|
457
467
|
const apiKey = options?.apiKey || cachedApiKey || "";
|
|
458
468
|
if (!apiKey) {
|
|
@@ -469,12 +479,58 @@ export function streamCoral(
|
|
|
469
479
|
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
|
|
470
480
|
const { reasoning: _reasoning, ...streamOptions } = options ?? {};
|
|
471
481
|
|
|
472
|
-
|
|
473
|
-
|
|
482
|
+
// Coral groups a conversation's turns by the first session id it finds: a
|
|
483
|
+
// session header, else the body's prompt_cache_key. pi-ai only sends the id as
|
|
484
|
+
// x-session-affinity on Chat Completions when a model opts in (forced below),
|
|
485
|
+
// and omits prompt_cache_key there for every base URL but api.openai.com, so
|
|
486
|
+
// send Coral's own header on both surfaces and the body key on Completions.
|
|
487
|
+
const sessionId = (streamOptions as any).sessionId as string | undefined;
|
|
488
|
+
const sessionHeaders = sessionId
|
|
489
|
+
? { "x-coral-session": sessionId, ...((streamOptions as any).headers ?? {}) }
|
|
490
|
+
: (streamOptions as any).headers;
|
|
491
|
+
const promptCacheKey = (streamOptions as any).cacheRetention === "none" ? undefined : clampPromptCacheKey(sessionId);
|
|
492
|
+
const withPromptCacheKey = (params: any) => {
|
|
493
|
+
if (!promptCacheKey || params?.prompt_cache_key !== undefined) return params;
|
|
494
|
+
return { ...params, prompt_cache_key: promptCacheKey };
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const compat = { ...model.compat, sendSessionAffinityHeaders: true };
|
|
498
|
+
const streamModel = { ...model, compat };
|
|
499
|
+
|
|
500
|
+
if (streamModel.api !== "openai-responses") {
|
|
501
|
+
const onPayload = (streamOptions as any).onPayload;
|
|
502
|
+
return streamOpenAICompletions(streamModel, context, {
|
|
503
|
+
...streamOptions,
|
|
504
|
+
headers: sessionHeaders,
|
|
505
|
+
reasoningEffort,
|
|
506
|
+
apiKey,
|
|
507
|
+
onPayload: async (params: any, payloadModel: any) => withPromptCacheKey((await onPayload?.(params, payloadModel)) ?? params),
|
|
508
|
+
} as any);
|
|
509
|
+
}
|
|
510
|
+
if (!settings.park) {
|
|
511
|
+
return streamOpenAIResponses(streamModel, context, { ...streamOptions, headers: sessionHeaders, reasoningEffort, apiKey } as any);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
let plan: ParkPlan | undefined;
|
|
515
|
+
const baseFetch = (streamOptions as any).fetch ?? ((input: any, init?: any) => globalThis.fetch(input, init));
|
|
516
|
+
const stream = streamOpenAIResponses(streamModel, context, {
|
|
474
517
|
...streamOptions,
|
|
518
|
+
headers: sessionHeaders,
|
|
475
519
|
reasoningEffort,
|
|
476
520
|
apiKey,
|
|
521
|
+
fetch: withParkFallback(baseFetch, () => plan),
|
|
522
|
+
onPayload: async (params: any, payloadModel: any) => {
|
|
523
|
+
const replaced = await options?.onPayload?.(params, payloadModel);
|
|
524
|
+
plan = planPark(withPromptCacheKey(replaced ?? params), context?.messages, model);
|
|
525
|
+
return plan.params;
|
|
526
|
+
},
|
|
477
527
|
} as any);
|
|
528
|
+
void stream.result().then((message: any) => {
|
|
529
|
+
if (plan && message?.responseId && message.stopReason !== "error" && message.stopReason !== "aborted") {
|
|
530
|
+
park(message.responseId, model.id, plan.input);
|
|
531
|
+
}
|
|
532
|
+
}, () => {});
|
|
533
|
+
return stream;
|
|
478
534
|
}
|
|
479
535
|
|
|
480
536
|
// API Key Resolution (via ModelRegistry)
|
|
@@ -504,7 +560,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
504
560
|
models: currentModels,
|
|
505
561
|
// An already-selected model may still carry the previous API. Snapshot
|
|
506
562
|
// the current selection per request, without mutating that model/history.
|
|
507
|
-
streamSimple: (model, context, options) => streamCoral({ ...model, api: selectedApi() }, context, options),
|
|
563
|
+
streamSimple: (model, context, options) => streamCoral({ ...model, api: selectedApi() }, context, options, { park: config.park }),
|
|
508
564
|
});
|
|
509
565
|
}
|
|
510
566
|
registerProvider();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-coralbricks-provider",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30",
|
|
4
4
|
"description": "CoralBricks provider extension for pi - Access GLM 5.3 and DeepSeek V4.1 Flash (FP4 variants included) through the Coral Inference API",
|
|
5
5
|
"author": "monotykamary",
|
|
6
6
|
"homepage": "https://github.com/monotykamary/pi-coralbricks-provider#readme",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"files": [
|
|
39
39
|
"index.ts",
|
|
40
40
|
"settings.ts",
|
|
41
|
+
"park.ts",
|
|
41
42
|
"models.json",
|
|
42
43
|
"deprecated-models.json",
|
|
43
44
|
"custom-models.json",
|
package/park.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parking: continue Coral Responses turns with `previous_response_id`.
|
|
3
|
+
*
|
|
4
|
+
* pi replays the whole transcript on every request. With parking on, each
|
|
5
|
+
* response is stored on Coral (`store: true`) and the next request sends only
|
|
6
|
+
* the items that came after it (tool results, the next user message). Coral
|
|
7
|
+
* keeps the parked state, sticky-routes the chain to the same replica and
|
|
8
|
+
* bills only the new input.
|
|
9
|
+
*
|
|
10
|
+
* A chain is used only when the full input pi would have sent starts with
|
|
11
|
+
* exactly the input of the parked request, followed by that response's own
|
|
12
|
+
* output items. Anything else (compaction, a model switch, an edited or
|
|
13
|
+
* branched history, a restart) sends the full transcript, which parks again.
|
|
14
|
+
* If Coral no longer has the parent (`previous_response_not_found`), the same
|
|
15
|
+
* request is re-sent once in full.
|
|
16
|
+
*/
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
|
|
19
|
+
type Item = Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
interface ParkedTurn {
|
|
22
|
+
model: string;
|
|
23
|
+
/** Digests of the full input sent with the request that produced this response. */
|
|
24
|
+
input: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ParkPlan {
|
|
28
|
+
/** Request body to send. */
|
|
29
|
+
params: Record<string, any>;
|
|
30
|
+
/** Full-transcript body to re-send if the parent is gone; undefined when not chained. */
|
|
31
|
+
fallback?: Record<string, any>;
|
|
32
|
+
/** Digests of the full input, recorded against the response id on success. */
|
|
33
|
+
input: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const MAX_PARKED = 256;
|
|
37
|
+
const parked = new Map<string, ParkedTurn>();
|
|
38
|
+
|
|
39
|
+
function digest(item: unknown): string {
|
|
40
|
+
return createHash("sha256").update(JSON.stringify(item)).digest("base64url");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isOutputItem(item: Item): boolean {
|
|
44
|
+
return item.type === "reasoning"
|
|
45
|
+
|| item.type === "function_call"
|
|
46
|
+
|| item.type === "custom_tool_call"
|
|
47
|
+
|| (item.type === "message" && item.role === "assistant");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The last assistant message, if it is a completed Coral Responses turn from this model. */
|
|
51
|
+
function parentId(messages: any[] | undefined, model: { id: string; provider: string }): string | undefined {
|
|
52
|
+
for (let i = (messages?.length ?? 0) - 1; i >= 0; i--) {
|
|
53
|
+
const msg = messages![i];
|
|
54
|
+
if (msg?.role !== "assistant") continue;
|
|
55
|
+
const ok = typeof msg.responseId === "string"
|
|
56
|
+
&& msg.provider === model.provider
|
|
57
|
+
&& msg.api === "openai-responses"
|
|
58
|
+
&& msg.model === model.id
|
|
59
|
+
&& msg.stopReason !== "error"
|
|
60
|
+
&& msg.stopReason !== "aborted";
|
|
61
|
+
return ok ? msg.responseId : undefined;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function planPark(params: Record<string, any>, messages: any[] | undefined, model: { id: string; provider: string }): ParkPlan {
|
|
67
|
+
const full: Item[] = Array.isArray(params.input) ? params.input : [];
|
|
68
|
+
const input = full.map(digest);
|
|
69
|
+
const stored: Record<string, any> = { ...params, store: true };
|
|
70
|
+
delete stored.previous_response_id;
|
|
71
|
+
|
|
72
|
+
const id = parentId(messages, model);
|
|
73
|
+
const turn = id ? parked.get(id) : undefined;
|
|
74
|
+
if (!id || !turn || turn.model !== model.id || turn.input.length > input.length) return { params: stored, input };
|
|
75
|
+
for (let i = 0; i < turn.input.length; i++) {
|
|
76
|
+
if (turn.input[i] !== input[i]) return { params: stored, input };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Skip the parent's own output; Coral already holds it.
|
|
80
|
+
let start = turn.input.length;
|
|
81
|
+
while (start < full.length && isOutputItem(full[start])) start++;
|
|
82
|
+
const delta = full.slice(start);
|
|
83
|
+
if (delta.length === 0 || delta.some(isOutputItem)) return { params: stored, input };
|
|
84
|
+
|
|
85
|
+
return { params: { ...stored, previous_response_id: id, input: delta }, fallback: stored, input };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function park(responseId: string, model: string, input: string[]): void {
|
|
89
|
+
parked.delete(responseId);
|
|
90
|
+
parked.set(responseId, { model, input });
|
|
91
|
+
while (parked.size > MAX_PARKED) parked.delete(parked.keys().next().value!);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function forget(responseId: string): void {
|
|
95
|
+
parked.delete(responseId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function clearParked(): void {
|
|
99
|
+
parked.clear();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function isParentMissing(status: number, body: string): boolean {
|
|
103
|
+
return (status === 404 || status === 400) && body.includes("previous_response_not_found");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Wrap fetch so a chained request whose parent Coral no longer has is re-sent
|
|
108
|
+
* once with the full transcript. Every other response passes through untouched.
|
|
109
|
+
*/
|
|
110
|
+
export function withParkFallback(base: typeof fetch, plan: () => ParkPlan | undefined): typeof fetch {
|
|
111
|
+
return async (input: any, init?: any) => {
|
|
112
|
+
const response = await base(input, init);
|
|
113
|
+
const current = plan();
|
|
114
|
+
if (response.ok || !current?.fallback) return response;
|
|
115
|
+
const body = await response.clone().text().catch(() => "");
|
|
116
|
+
if (!isParentMissing(response.status, body)) return response;
|
|
117
|
+
forget(current.params.previous_response_id);
|
|
118
|
+
const headers = new Headers(init?.headers);
|
|
119
|
+
headers.delete("content-length");
|
|
120
|
+
return base(input, { ...init, headers, body: JSON.stringify(current.fallback) });
|
|
121
|
+
};
|
|
122
|
+
}
|
package/settings.ts
CHANGED
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
|
|
6
6
|
export type CoralApi = "chat-completions" | "responses";
|
|
7
|
-
export interface CoralConfig { api: CoralApi }
|
|
7
|
+
export interface CoralConfig { api: CoralApi; park: boolean }
|
|
8
8
|
|
|
9
9
|
export function configPath(): string {
|
|
10
10
|
return path.join(getAgentDir(), "extensions", "coralbricks.json");
|
|
@@ -23,16 +23,16 @@ function readRawConfig(): Record<string, unknown> {
|
|
|
23
23
|
|
|
24
24
|
export function loadConfig(): CoralConfig {
|
|
25
25
|
try {
|
|
26
|
-
|
|
26
|
+
const raw = readRawConfig();
|
|
27
|
+
return { api: raw.api === "responses" ? "responses" : "chat-completions", park: raw.park === true };
|
|
27
28
|
} catch {
|
|
28
|
-
return { api: "chat-completions" };
|
|
29
|
+
return { api: "chat-completions", park: false };
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/** Preserve unrelated fields; never overwrite malformed settings. */
|
|
33
|
-
export function
|
|
34
|
-
const raw = readRawConfig();
|
|
35
|
-
raw.api = api;
|
|
34
|
+
export function saveConfig(patch: Partial<CoralConfig>): void {
|
|
35
|
+
const raw = { ...readRawConfig(), ...patch };
|
|
36
36
|
const file = configPath();
|
|
37
37
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
38
38
|
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
@@ -44,35 +44,49 @@ export function saveApi(api: CoralApi): void {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export function saveApi(api: CoralApi): void {
|
|
48
|
+
saveConfig({ api });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const PARK_DESCRIPTION = "Responses only. Stores each response on Coral (store:true) and sends only new items with previous_response_id. Falls back to a full replay when the chain does not match.";
|
|
52
|
+
|
|
47
53
|
export function registerSettingsCommand(
|
|
48
54
|
pi: ExtensionAPI,
|
|
49
55
|
getConfig: () => CoralConfig,
|
|
50
56
|
apply: (config: CoralConfig) => void,
|
|
51
57
|
): void {
|
|
52
58
|
pi.registerCommand("coralbricks-settings", {
|
|
53
|
-
description: "Configure CoralBricks API
|
|
59
|
+
description: "Configure CoralBricks API (Chat Completions or opt-in Responses) and Responses parking",
|
|
54
60
|
async handler(_args, ctx) {
|
|
55
61
|
if (!ctx.hasUI) {
|
|
56
62
|
ctx.ui.notify("/coralbricks-settings requires a UI (TUI or GUI).", "error");
|
|
57
63
|
return;
|
|
58
64
|
}
|
|
59
65
|
const values: CoralApi[] = ["chat-completions", "responses"];
|
|
60
|
-
const
|
|
61
|
-
|
|
66
|
+
const parkValues = ["off", "on"];
|
|
67
|
+
const change = (id: string, value: string): boolean => {
|
|
68
|
+
let patch: Partial<CoralConfig>;
|
|
69
|
+
if (id === "api" && values.includes(value as CoralApi)) patch = { api: value as CoralApi };
|
|
70
|
+
else if (id === "park" && parkValues.includes(value)) patch = { park: value === "on" };
|
|
71
|
+
else return false;
|
|
62
72
|
try {
|
|
63
|
-
|
|
73
|
+
saveConfig(patch);
|
|
64
74
|
} catch {
|
|
65
75
|
ctx.ui.notify(`Could not save ${configPath()}. Check its JSON and permissions; API unchanged.`, "error");
|
|
66
76
|
return false;
|
|
67
77
|
}
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
const next = { ...getConfig(), ...patch };
|
|
79
|
+
apply(next);
|
|
80
|
+
const label = id === "api" ? `CoralBricks API: ${value}` : `CoralBricks parking: ${value}`;
|
|
81
|
+
ctx.ui.notify(`${label} — applies to the next request.`, "info");
|
|
70
82
|
return true;
|
|
71
83
|
};
|
|
72
84
|
|
|
73
85
|
if (ctx.mode !== "tui") {
|
|
74
86
|
const selected = await ctx.ui.select(`CoralBricks API (current: ${getConfig().api})`, values);
|
|
75
|
-
if (selected
|
|
87
|
+
if (selected === undefined || !change("api", selected) || selected !== "responses") return;
|
|
88
|
+
const park = await ctx.ui.select(`Park Responses turns with previous_response_id? (current: ${getConfig().park ? "on" : "off"})`, parkValues);
|
|
89
|
+
if (park !== undefined) change("park", park);
|
|
76
90
|
return;
|
|
77
91
|
}
|
|
78
92
|
|
|
@@ -87,12 +101,19 @@ export function registerSettingsCommand(
|
|
|
87
101
|
{
|
|
88
102
|
id: "api",
|
|
89
103
|
label: "API surface",
|
|
90
|
-
description: "Chat Completions is the default. Responses is opt-in
|
|
104
|
+
description: "Chat Completions is the default. Responses is opt-in; it replays full history with store:false unless parking is on.",
|
|
91
105
|
currentValue: getConfig().api,
|
|
92
106
|
values,
|
|
93
107
|
},
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
{
|
|
109
|
+
id: "park",
|
|
110
|
+
label: "Park Responses turns",
|
|
111
|
+
description: PARK_DESCRIPTION,
|
|
112
|
+
currentValue: getConfig().park ? "on" : "off",
|
|
113
|
+
values: parkValues,
|
|
114
|
+
},
|
|
115
|
+
], 3, getSettingsListTheme(), (id, value) => {
|
|
116
|
+
if (!change(id, value)) list.updateValue(id, id === "api" ? getConfig().api : getConfig().park ? "on" : "off");
|
|
96
117
|
}, () => done(), { enableSearch: true });
|
|
97
118
|
container.addChild(list);
|
|
98
119
|
container.addChild(border());
|