pi-openai-toolkit 0.7.1 → 0.8.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 +33 -3
- package/README_zh.md +36 -3
- package/extensions/image-generation.ts +1 -0
- package/package.json +20 -4
- package/src/compact-client.ts +4 -51
- package/src/config.ts +31 -1
- package/src/extension-runtime.ts +2 -4
- package/src/image-generation/artifacts.ts +237 -0
- package/src/image-generation/client.ts +248 -0
- package/src/image-generation/eligibility.ts +18 -0
- package/src/image-generation/extension.ts +160 -0
- package/src/image-generation/protocol.ts +405 -0
- package/src/image-generation/references.ts +194 -0
- package/src/image-generation/render.ts +252 -0
- package/src/image-generation/service.ts +243 -0
- package/src/image-generation/types.ts +184 -0
- package/src/model-scope.ts +17 -0
- package/src/payload-rewrite.ts +17 -146
- package/src/remote-v2-client.ts +7 -51
- package/src/responses-headers.ts +67 -0
- package/src/runtime.ts +43 -4
- package/src/types.ts +14 -0
- package/src/web-search/types.ts +3 -5
- package/tsconfig.check.json +12 -0
package/src/payload-rewrite.ts
CHANGED
|
@@ -9,31 +9,16 @@ import type {
|
|
|
9
9
|
import type { ResponsesCompatibleRequestPayload } from "./runtime";
|
|
10
10
|
import { rewritePayloadWithDeferredToolCarryover } from "./deferred-tool-carryover";
|
|
11
11
|
import type { NativeCompactionEntry } from "./types";
|
|
12
|
-
import {
|
|
13
|
-
compareResponsesInputParity,
|
|
14
|
-
serializeMessagesToResponsesInput,
|
|
15
|
-
type ResponsesInputContentItem,
|
|
16
|
-
type ResponsesInputItem,
|
|
17
|
-
type ResponsesInputMessageItem,
|
|
18
|
-
} from "./serializer";
|
|
19
|
-
|
|
20
|
-
export type SerializedReplaySlice = {
|
|
21
|
-
entries: SessionEntry[];
|
|
22
|
-
messages: AgentMessage[];
|
|
23
|
-
input: ResponsesInputItem[];
|
|
24
|
-
};
|
|
12
|
+
import { serializeMessagesToResponsesInput, type ResponsesInputContentItem, type ResponsesInputItem, type ResponsesInputMessageItem } from "./serializer";
|
|
25
13
|
|
|
26
14
|
export type NativeReplaySegments = {
|
|
27
15
|
boundaryIndex: number;
|
|
28
16
|
firstKeptEntryIndex: number;
|
|
29
17
|
instructions?: string;
|
|
30
|
-
|
|
31
|
-
trailingPreamble: ResponsesInputMessageItem[];
|
|
18
|
+
leading: ResponsesInputItem[];
|
|
32
19
|
compactionSummary: ResponsesInputItem[];
|
|
33
|
-
preCompactionKeptWindow: SerializedReplaySlice;
|
|
34
20
|
compactedWindow: unknown[];
|
|
35
|
-
|
|
36
|
-
originalPiReplayInput: ResponsesInputItem[];
|
|
21
|
+
post: ResponsesInputItem[];
|
|
37
22
|
replayInput: unknown[];
|
|
38
23
|
};
|
|
39
24
|
|
|
@@ -48,7 +33,6 @@ export type NativeReplayPayloadRewriteFailureReason =
|
|
|
48
33
|
| "first-kept-entry-not-found"
|
|
49
34
|
| "invalid-compacted-window"
|
|
50
35
|
| "unexpected-compaction-after-boundary"
|
|
51
|
-
| "expected-pi-replay-mismatch"
|
|
52
36
|
| "compaction-summary-not-found";
|
|
53
37
|
|
|
54
38
|
export type NativeReplayPayloadRewriteFailure = {
|
|
@@ -89,19 +73,6 @@ function isResponsesInputMessageRole(value: unknown): value is ResponsesInputMes
|
|
|
89
73
|
return value === "user" || value === "developer" || value === "system";
|
|
90
74
|
}
|
|
91
75
|
|
|
92
|
-
function isPreambleRole(value: ResponsesInputMessageItem["role"]): value is "developer" | "system" {
|
|
93
|
-
return value === "developer" || value === "system";
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function isResponsesInputMessageItem(value: unknown): value is ResponsesInputMessageItem {
|
|
97
|
-
if (!isRecord(value) || !isResponsesInputMessageRole(value.role)) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const { content } = value;
|
|
102
|
-
return typeof content === "string" || (Array.isArray(content) && content.every(isResponsesInputContentItem));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
76
|
function cloneResponsesInputContentItem(item: ResponsesInputContentItem): ResponsesInputContentItem {
|
|
106
77
|
return item.type === "input_text"
|
|
107
78
|
? {
|
|
@@ -294,20 +265,6 @@ function findCompactionSummaryIndex(input: readonly unknown[], summaryMarker: st
|
|
|
294
265
|
});
|
|
295
266
|
}
|
|
296
267
|
|
|
297
|
-
/** Count consecutive provider-authored developer/system items at the end of the input. */
|
|
298
|
-
function countTrailingPreambleItems(items: readonly unknown[]): number {
|
|
299
|
-
let count = 0;
|
|
300
|
-
for (let index = items.length - 1; index >= 0; index--) {
|
|
301
|
-
const item = items[index];
|
|
302
|
-
if (isResponsesInputMessageItem(item) && isPreambleRole(item.role)) {
|
|
303
|
-
count += 1;
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
return count;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
268
|
function collectReplayMessages(entries: readonly SessionEntry[]): AgentMessage[] {
|
|
312
269
|
const messages: AgentMessage[] = [];
|
|
313
270
|
|
|
@@ -321,27 +278,6 @@ function collectReplayMessages(entries: readonly SessionEntry[]): AgentMessage[]
|
|
|
321
278
|
return messages;
|
|
322
279
|
}
|
|
323
280
|
|
|
324
|
-
function createCompactionSummaryAgentMessage(entry: NativeCompactionEntry): AgentMessage {
|
|
325
|
-
return {
|
|
326
|
-
role: "compactionSummary",
|
|
327
|
-
summary: entry.summary,
|
|
328
|
-
tokensBefore: entry.tokensBefore,
|
|
329
|
-
timestamp: new Date(entry.timestamp).getTime(),
|
|
330
|
-
} as AgentMessage;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function createReplaySlice(
|
|
334
|
-
entries: readonly SessionEntry[],
|
|
335
|
-
messages: readonly AgentMessage[],
|
|
336
|
-
input: readonly ResponsesInputItem[],
|
|
337
|
-
): SerializedReplaySlice {
|
|
338
|
-
return {
|
|
339
|
-
entries: [...entries],
|
|
340
|
-
messages: [...messages],
|
|
341
|
-
input: [...input],
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
|
|
345
281
|
function findEntryIndexByIdBeforeBoundary(
|
|
346
282
|
entries: readonly SessionEntry[],
|
|
347
283
|
entryId: string,
|
|
@@ -436,14 +372,13 @@ function buildNativeReplaySegmentsInternal<TApi extends Api>(args: {
|
|
|
436
372
|
|
|
437
373
|
const preCompactionEntries = args.branchEntries.slice(firstKeptEntryIndex, boundaryIndex);
|
|
438
374
|
const postCompactionEntries = args.branchEntries.slice(boundaryIndex + 1);
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
// needed; the marker text is unique to this compaction entry.
|
|
375
|
+
|
|
376
|
+
// Pi-authored compaction summary item. Pi 0.84.3 renders the context after a
|
|
377
|
+
// compaction as [leading prompts][summary item][all post-compaction messages]:
|
|
378
|
+
// pre-compaction messages never appear in the payload (entry.retainedTail is
|
|
379
|
+
// never persisted by appendCompaction), so the summary item itself is the only
|
|
380
|
+
// anchor and the only thing that gets replaced. Everything else is preserved
|
|
381
|
+
// verbatim, so transient messages injected by other extensions stay intact.
|
|
447
382
|
const summaryIndex = findCompactionSummaryIndex(args.payload.input, args.compactionEntry.summary);
|
|
448
383
|
if (summaryIndex < 0) {
|
|
449
384
|
return {
|
|
@@ -452,66 +387,19 @@ function buildNativeReplaySegmentsInternal<TApi extends Api>(args: {
|
|
|
452
387
|
};
|
|
453
388
|
}
|
|
454
389
|
|
|
455
|
-
const keptStart = summaryIndex + 1;
|
|
456
|
-
const keptEnd = keptStart + keptWindowSerialized.length;
|
|
457
|
-
const actualKeptWindow = cloneResponsesInputSlice(args.payload.input.slice(keptStart, keptEnd));
|
|
458
|
-
if (!actualKeptWindow) {
|
|
459
|
-
return {
|
|
460
|
-
ok: false,
|
|
461
|
-
reason: "expected-pi-replay-mismatch",
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// The only hard verification: the region immediately after the sentinel must be
|
|
466
|
-
// exactly the kept pre-compaction window derived from session entries. Content
|
|
467
|
-
// after this region is preserved as-is, so transient messages injected by other
|
|
468
|
-
// extensions (which have no session entries) no longer break replay.
|
|
469
|
-
if (!areEquivalentValues(actualKeptWindow, keptWindowSerialized)) {
|
|
470
|
-
const parity = compareResponsesInputParity(
|
|
471
|
-
args.payload.input.slice(keptStart, keptEnd),
|
|
472
|
-
keptWindowSerialized,
|
|
473
|
-
);
|
|
474
|
-
return {
|
|
475
|
-
ok: false,
|
|
476
|
-
reason: "expected-pi-replay-mismatch",
|
|
477
|
-
parity: {
|
|
478
|
-
actual: parity.actual,
|
|
479
|
-
expected: parity.expected,
|
|
480
|
-
mismatches: parity.mismatches,
|
|
481
|
-
},
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const actualCompactionSummary = cloneResponsesInputSlice([args.payload.input[summaryIndex]]);
|
|
486
|
-
const actualPostCompactionTail = cloneResponsesInputSlice(args.payload.input.slice(keptEnd));
|
|
487
|
-
if (!actualCompactionSummary || !actualPostCompactionTail) {
|
|
488
|
-
return {
|
|
489
|
-
ok: false,
|
|
490
|
-
reason: "expected-pi-replay-mismatch",
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Keep provider-authored trailing developer/system prompts in place.
|
|
495
|
-
const trailingCount = countTrailingPreambleItems(actualPostCompactionTail);
|
|
496
|
-
const trailingPreamble = actualPostCompactionTail.slice(actualPostCompactionTail.length - trailingCount);
|
|
497
|
-
const liveTail = actualPostCompactionTail.slice(0, actualPostCompactionTail.length - trailingCount);
|
|
498
|
-
|
|
499
390
|
const leadingInput = cloneResponsesInputSlice(args.payload.input.slice(0, summaryIndex));
|
|
500
|
-
|
|
391
|
+
const actualCompactionSummary = cloneResponsesInputSlice([args.payload.input[summaryIndex]]);
|
|
392
|
+
const actualPostInput = cloneResponsesInputSlice(args.payload.input.slice(summaryIndex + 1));
|
|
393
|
+
if (!leadingInput || !actualCompactionSummary || !actualPostInput) {
|
|
501
394
|
return {
|
|
502
395
|
ok: false,
|
|
503
|
-
reason: "
|
|
396
|
+
reason: "compaction-summary-not-found",
|
|
504
397
|
};
|
|
505
398
|
}
|
|
506
399
|
|
|
507
400
|
const baseRewrittenPayload: ResponsesCompatibleRequestPayload = {
|
|
508
401
|
...args.payload,
|
|
509
|
-
input: [
|
|
510
|
-
...leadingInput,
|
|
511
|
-
...compactedWindow,
|
|
512
|
-
...liveTail,
|
|
513
|
-
...trailingPreamble,
|
|
514
|
-
],
|
|
402
|
+
input: [...leadingInput, ...compactedWindow, ...actualPostInput],
|
|
515
403
|
};
|
|
516
404
|
const carryoverRewrite = rewritePayloadWithDeferredToolCarryover({
|
|
517
405
|
payload: baseRewrittenPayload,
|
|
@@ -523,33 +411,16 @@ function buildNativeReplaySegmentsInternal<TApi extends Api>(args: {
|
|
|
523
411
|
| undefined,
|
|
524
412
|
});
|
|
525
413
|
|
|
526
|
-
const sentinelSerialized = serializeMessagesToResponsesInput(args.model, [compactionSummaryMessage]);
|
|
527
|
-
const serializedTailInput = serializeMessagesToResponsesInput(args.model, postCompactionTailMessages);
|
|
528
|
-
const originalPiReplayInput: ResponsesInputItem[] = [
|
|
529
|
-
...leadingInput,
|
|
530
|
-
...sentinelSerialized,
|
|
531
|
-
...keptWindowSerialized,
|
|
532
|
-
...serializedTailInput,
|
|
533
|
-
...trailingPreamble,
|
|
534
|
-
];
|
|
535
|
-
|
|
536
414
|
return {
|
|
537
415
|
ok: true,
|
|
538
416
|
segments: {
|
|
539
417
|
boundaryIndex,
|
|
540
418
|
firstKeptEntryIndex,
|
|
541
419
|
instructions: typeof args.payload.instructions === "string" ? args.payload.instructions : undefined,
|
|
542
|
-
|
|
543
|
-
trailingPreamble: trailingPreamble as ResponsesInputMessageItem[],
|
|
420
|
+
leading: leadingInput,
|
|
544
421
|
compactionSummary: actualCompactionSummary,
|
|
545
|
-
preCompactionKeptWindow: createReplaySlice(
|
|
546
|
-
preCompactionEntries,
|
|
547
|
-
preCompactionKeptMessages,
|
|
548
|
-
actualKeptWindow,
|
|
549
|
-
),
|
|
550
422
|
compactedWindow,
|
|
551
|
-
|
|
552
|
-
originalPiReplayInput,
|
|
423
|
+
post: actualPostInput,
|
|
553
424
|
replayInput: carryoverRewrite.payload.input,
|
|
554
425
|
},
|
|
555
426
|
rewrittenPayload: carryoverRewrite.payload,
|
package/src/remote-v2-client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writeDebugArtifact } from "./debug";
|
|
2
|
-
import {
|
|
2
|
+
import { buildResponsesRequestHeaders } from "./responses-headers";
|
|
3
3
|
import type { NativeCompactionRuntime } from "./runtime";
|
|
4
4
|
import type { NativeCompactionRequestBody, ResponsesInputItem } from "./serializer";
|
|
5
5
|
import type { ArtifactContext, CompactionConfig } from "./types";
|
|
@@ -114,57 +114,13 @@ function normalizeResponseTimestamp(value: unknown): string | undefined {
|
|
|
114
114
|
return Number.isNaN(parsed) ? trimmed : new Date(parsed).toISOString();
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
|
|
118
|
-
const parts = token.split(".");
|
|
119
|
-
if (parts.length !== 3) {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const payloadText = Buffer.from(parts[1]!, "base64url").toString("utf8");
|
|
125
|
-
const payload = JSON.parse(payloadText);
|
|
126
|
-
return isRecord(payload) ? payload : undefined;
|
|
127
|
-
} catch {
|
|
128
|
-
return undefined;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function extractCodexAccountId(token: string): string | undefined {
|
|
133
|
-
const payload = decodeJwtPayload(token);
|
|
134
|
-
const authClaims = payload?.["https://api.openai.com/auth"];
|
|
135
|
-
if (!isRecord(authClaims)) {
|
|
136
|
-
return undefined;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const accountId = authClaims.chatgpt_account_id;
|
|
140
|
-
return isNonEmptyString(accountId) ? accountId.trim() : undefined;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function buildCodexUserAgent(): string {
|
|
144
|
-
const platform = typeof process !== "undefined" ? process.platform : "browser";
|
|
145
|
-
const arch = typeof process !== "undefined" ? process.arch : "unknown";
|
|
146
|
-
return `pi (${platform}; ${arch})`;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
117
|
function toHeaders(runtime: NativeCompactionRuntime): Record<string, string> {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (runtime.api === "openai-codex-responses") {
|
|
158
|
-
const accountId = extractCodexAccountId(runtime.apiKey);
|
|
159
|
-
if (accountId) {
|
|
160
|
-
headers.set("chatgpt-account-id", accountId);
|
|
161
|
-
}
|
|
162
|
-
headers.set("originator", "pi");
|
|
163
|
-
headers.set("user-agent", buildCodexUserAgent());
|
|
164
|
-
headers.set("openai-beta", "responses=experimental");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return Object.fromEntries(headers.entries());
|
|
118
|
+
return Object.fromEntries(
|
|
119
|
+
buildResponsesRequestHeaders(runtime, {
|
|
120
|
+
accept: SSE_CONTENT_TYPE,
|
|
121
|
+
contentType: JSON_CONTENT_TYPE,
|
|
122
|
+
}).entries(),
|
|
123
|
+
);
|
|
168
124
|
}
|
|
169
125
|
|
|
170
126
|
export function buildRemoteV2CompactionRequest(
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ProviderHeaders } from "@earendil-works/pi-ai";
|
|
2
|
+
import { mergeProviderHeaders } from "./provider-headers";
|
|
3
|
+
|
|
4
|
+
export type ResponsesHeaderRuntime = {
|
|
5
|
+
api: string;
|
|
6
|
+
apiKey: string;
|
|
7
|
+
headers?: ProviderHeaders;
|
|
8
|
+
currentModel: {
|
|
9
|
+
headers?: ProviderHeaders;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
14
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
|
|
18
|
+
const parts = token.split(".");
|
|
19
|
+
if (parts.length !== 3) return undefined;
|
|
20
|
+
try {
|
|
21
|
+
const payload = JSON.parse(Buffer.from(parts[1]!, "base64url").toString("utf8")) as unknown;
|
|
22
|
+
return isRecord(payload) ? payload : undefined;
|
|
23
|
+
} catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractCodexAccountId(token: string): string | undefined {
|
|
29
|
+
const authClaims = decodeJwtPayload(token)?.["https://api.openai.com/auth"];
|
|
30
|
+
if (!isRecord(authClaims)) return undefined;
|
|
31
|
+
const accountId = authClaims.chatgpt_account_id;
|
|
32
|
+
return typeof accountId === "string" && accountId.trim() ? accountId.trim() : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildCodexUserAgent(): string {
|
|
36
|
+
const platform = typeof process !== "undefined" ? process.platform : "browser";
|
|
37
|
+
const arch = typeof process !== "undefined" ? process.arch : "unknown";
|
|
38
|
+
return `pi (${platform}; ${arch})`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildResponsesRequestHeaders(
|
|
42
|
+
runtime: ResponsesHeaderRuntime,
|
|
43
|
+
options: { accept: string; contentType?: string },
|
|
44
|
+
): Headers {
|
|
45
|
+
const headers = new Headers(
|
|
46
|
+
mergeProviderHeaders(runtime.currentModel.headers, runtime.headers),
|
|
47
|
+
);
|
|
48
|
+
headers.set("accept", options.accept);
|
|
49
|
+
headers.set("content-type", options.contentType ?? "application/json");
|
|
50
|
+
if (!headers.has("authorization")) {
|
|
51
|
+
headers.set("authorization", `Bearer ${runtime.apiKey}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (runtime.api === "openai-codex-responses") {
|
|
55
|
+
const accountId = extractCodexAccountId(runtime.apiKey);
|
|
56
|
+
if (accountId) headers.set("chatgpt-account-id", accountId);
|
|
57
|
+
headers.set("originator", "pi");
|
|
58
|
+
headers.set("user-agent", buildCodexUserAgent());
|
|
59
|
+
headers.set("openai-beta", "responses=experimental");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return headers;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const _responsesHeadersTest = {
|
|
66
|
+
extractCodexAccountId,
|
|
67
|
+
};
|
package/src/runtime.ts
CHANGED
|
@@ -7,8 +7,9 @@ const CODEX_RESPONSES_PATH = "codex/responses";
|
|
|
7
7
|
const OPENAI_COMPACT_PATH = "responses/compact";
|
|
8
8
|
const CODEX_COMPACT_PATH = "codex/responses/compact";
|
|
9
9
|
|
|
10
|
-
type
|
|
10
|
+
export type ResponsesApi = (typeof RESPONSES_COMPACT_CAPABLE_APIS)[number];
|
|
11
11
|
|
|
12
|
+
type ResponsesCompactApi = ResponsesApi;
|
|
12
13
|
type RuntimeModel = Model<Api>;
|
|
13
14
|
|
|
14
15
|
export type ParsedModelSpec = {
|
|
@@ -35,6 +36,8 @@ export type NativeCompactionSupportOptions = {
|
|
|
35
36
|
responsesApis?: readonly string[];
|
|
36
37
|
};
|
|
37
38
|
|
|
39
|
+
export type ResponsesSupportOptions = NativeCompactionSupportOptions;
|
|
40
|
+
|
|
38
41
|
export type ResponsesCompatibleRequestPayload = {
|
|
39
42
|
model: string;
|
|
40
43
|
input: unknown[];
|
|
@@ -42,19 +45,22 @@ export type ResponsesCompatibleRequestPayload = {
|
|
|
42
45
|
[key: string]: unknown;
|
|
43
46
|
};
|
|
44
47
|
|
|
45
|
-
export type
|
|
48
|
+
export type ResponsesRuntime = {
|
|
46
49
|
provider: string;
|
|
47
|
-
api:
|
|
50
|
+
api: ResponsesApi;
|
|
48
51
|
model: string;
|
|
49
52
|
baseUrl: string;
|
|
50
53
|
apiKey: string;
|
|
51
54
|
headers?: ProviderHeaders;
|
|
52
55
|
responsesPath: string;
|
|
53
56
|
responsesUrl: string;
|
|
57
|
+
currentModel: RuntimeModel;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type NativeCompactionRuntime = ResponsesRuntime & {
|
|
54
61
|
compactPath: string;
|
|
55
62
|
compactUrl: string;
|
|
56
63
|
payload?: ResponsesCompatibleRequestPayload;
|
|
57
|
-
currentModel: RuntimeModel;
|
|
58
64
|
};
|
|
59
65
|
|
|
60
66
|
export type NativeCompactionEnvironmentFailure = {
|
|
@@ -77,6 +83,15 @@ export type NativeCompactionEnvironmentResolution =
|
|
|
77
83
|
| NativeCompactionEnvironmentFailure
|
|
78
84
|
| NativeCompactionEnvironmentSuccess;
|
|
79
85
|
|
|
86
|
+
export type ResponsesEnvironmentSuccess = {
|
|
87
|
+
ok: true;
|
|
88
|
+
runtime: ResponsesRuntime;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type ResponsesEnvironmentResolution =
|
|
92
|
+
| NativeCompactionEnvironmentFailure
|
|
93
|
+
| ResponsesEnvironmentSuccess;
|
|
94
|
+
|
|
80
95
|
export type RemoteCompactionExecution = {
|
|
81
96
|
/** Active session model. Its identity owns replay matching and is never mutated. */
|
|
82
97
|
consumer: NativeCompactionRuntime;
|
|
@@ -327,6 +342,30 @@ async function resolveNativeCompactionEnvironmentForModel(
|
|
|
327
342
|
};
|
|
328
343
|
}
|
|
329
344
|
|
|
345
|
+
export async function resolveResponsesEnvironment(
|
|
346
|
+
ctx: ExtensionContext,
|
|
347
|
+
options: ResponsesSupportOptions = {},
|
|
348
|
+
): Promise<ResponsesEnvironmentResolution> {
|
|
349
|
+
const resolution = await resolveNativeCompactionEnvironmentForModel(ctx, ctx.model, options);
|
|
350
|
+
if (!resolution.ok) return resolution;
|
|
351
|
+
|
|
352
|
+
const runtime = resolution.runtime;
|
|
353
|
+
return {
|
|
354
|
+
ok: true,
|
|
355
|
+
runtime: {
|
|
356
|
+
provider: runtime.provider,
|
|
357
|
+
api: runtime.api,
|
|
358
|
+
model: runtime.model,
|
|
359
|
+
baseUrl: runtime.baseUrl,
|
|
360
|
+
apiKey: runtime.apiKey,
|
|
361
|
+
headers: runtime.headers,
|
|
362
|
+
responsesPath: runtime.responsesPath,
|
|
363
|
+
responsesUrl: runtime.responsesUrl,
|
|
364
|
+
currentModel: runtime.currentModel,
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
330
369
|
export async function resolveNativeCompactionEnvironment(
|
|
331
370
|
ctx: ExtensionContext,
|
|
332
371
|
options: NativeCompactionSupportOptions = {},
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { CompactionEntry, CompactionResult, ExtensionContext, SessionEntry
|
|
|
4
4
|
export const TOOLKIT_ID = "pi-openai-toolkit";
|
|
5
5
|
export const COMPACTION_EXTENSION_ID = `${TOOLKIT_ID}:compaction`;
|
|
6
6
|
export const WEB_SEARCH_EXTENSION_ID = `${TOOLKIT_ID}:web-search`;
|
|
7
|
+
export const IMAGE_GENERATION_EXTENSION_ID = `${TOOLKIT_ID}:image-generation`;
|
|
7
8
|
export const DEFAULT_ARTIFACT_ROOT = "~/.pi/agent/artifacts/pi-openai-toolkit/compaction";
|
|
8
9
|
export const REDACTED_VALUE = "[REDACTED]";
|
|
9
10
|
/**
|
|
@@ -82,9 +83,16 @@ export type WebSearchConfig = {
|
|
|
82
83
|
models: string[];
|
|
83
84
|
};
|
|
84
85
|
|
|
86
|
+
export type ImageGenerationConfig = {
|
|
87
|
+
enabled: boolean;
|
|
88
|
+
/** Exact provider/model keys allowed to use toolkit-native image generation. */
|
|
89
|
+
models: string[];
|
|
90
|
+
};
|
|
91
|
+
|
|
85
92
|
export type ToolkitConfig = {
|
|
86
93
|
compaction: CompactionConfig;
|
|
87
94
|
webSearch: WebSearchConfig;
|
|
95
|
+
imageGeneration: ImageGenerationConfig;
|
|
88
96
|
};
|
|
89
97
|
|
|
90
98
|
export type LoadedToolkitConfig = {
|
|
@@ -438,7 +446,13 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchConfig = {
|
|
|
438
446
|
models: [],
|
|
439
447
|
};
|
|
440
448
|
|
|
449
|
+
export const DEFAULT_IMAGE_GENERATION_CONFIG: ImageGenerationConfig = {
|
|
450
|
+
enabled: true,
|
|
451
|
+
models: [],
|
|
452
|
+
};
|
|
453
|
+
|
|
441
454
|
export const DEFAULT_TOOLKIT_CONFIG: ToolkitConfig = {
|
|
442
455
|
compaction: DEFAULT_COMPACTION_CONFIG,
|
|
443
456
|
webSearch: DEFAULT_WEB_SEARCH_CONFIG,
|
|
457
|
+
imageGeneration: DEFAULT_IMAGE_GENERATION_CONFIG,
|
|
444
458
|
};
|
package/src/web-search/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getExactModelKey, isExactModelAllowed } from "../model-scope";
|
|
1
2
|
import type { WebSearchConfig } from "../types";
|
|
2
3
|
|
|
3
4
|
export const WEB_SEARCH_CAPABLE_APIS = ["openai-responses", "openai-codex-responses"] as const;
|
|
@@ -28,20 +29,17 @@ export type WebSearchPayloadTransform = {
|
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
export function getWebSearchModelKey(model: WebSearchModel | undefined): string | undefined {
|
|
31
|
-
|
|
32
|
-
return `${model.provider}/${model.id}`;
|
|
32
|
+
return getExactModelKey(model);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export function isWebSearchEnabledForModel(
|
|
36
36
|
model: WebSearchModel | undefined,
|
|
37
37
|
config: WebSearchConfig,
|
|
38
38
|
): boolean {
|
|
39
|
-
const modelKey = getWebSearchModelKey(model);
|
|
40
39
|
return (
|
|
41
40
|
config.enabled &&
|
|
42
41
|
typeof model?.api === "string" &&
|
|
43
42
|
(WEB_SEARCH_CAPABLE_APIS as readonly string[]).includes(model.api) &&
|
|
44
|
-
|
|
45
|
-
config.models.includes(modelKey)
|
|
43
|
+
isExactModelAllowed(model, config.models)
|
|
46
44
|
);
|
|
47
45
|
}
|
package/tsconfig.check.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"include": [
|
|
15
15
|
"extensions/compaction.ts",
|
|
16
16
|
"extensions/web-search.ts",
|
|
17
|
+
"extensions/image-generation.ts",
|
|
17
18
|
"src/compact-client.ts",
|
|
18
19
|
"src/config.ts",
|
|
19
20
|
"src/debug.ts",
|
|
@@ -21,9 +22,20 @@
|
|
|
21
22
|
"src/deferred-tool-carryover.ts",
|
|
22
23
|
"src/inline-compaction.ts",
|
|
23
24
|
"src/extension-runtime.ts",
|
|
25
|
+
"src/image-generation/artifacts.ts",
|
|
26
|
+
"src/image-generation/client.ts",
|
|
27
|
+
"src/image-generation/eligibility.ts",
|
|
28
|
+
"src/image-generation/extension.ts",
|
|
29
|
+
"src/image-generation/protocol.ts",
|
|
30
|
+
"src/image-generation/references.ts",
|
|
31
|
+
"src/image-generation/render.ts",
|
|
32
|
+
"src/image-generation/service.ts",
|
|
33
|
+
"src/image-generation/types.ts",
|
|
34
|
+
"src/model-scope.ts",
|
|
24
35
|
"src/native-fallback.ts",
|
|
25
36
|
"src/payload-rewrite.ts",
|
|
26
37
|
"src/provider-headers.ts",
|
|
38
|
+
"src/responses-headers.ts",
|
|
27
39
|
"src/remote-v2-client.ts",
|
|
28
40
|
"src/request-context-cache.ts",
|
|
29
41
|
"src/runtime.ts",
|