@oh-my-pi/pi-ai 16.0.8 → 16.0.10
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/CHANGELOG.md +25 -0
- package/dist/types/providers/openai-shared.d.ts +3 -2
- package/dist/types/registry/oauth/types.d.ts +6 -0
- package/package.json +3 -3
- package/src/auth-storage.ts +83 -9
- package/src/dialect/owned-stream.ts +1 -1
- package/src/dialect/pi.md +31 -33
- package/src/dialect/pi.ts +339 -452
- package/src/providers/amazon-bedrock.ts +13 -1
- package/src/providers/azure-openai-responses.ts +1 -0
- package/src/providers/openai-codex-responses.ts +13 -2
- package/src/providers/openai-responses.ts +1 -0
- package/src/providers/openai-shared.ts +25 -3
- package/src/registry/oauth/index.ts +2 -0
- package/src/registry/oauth/types.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.0.10] - 2026-06-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Replaced the old legacy XML-ish `pi` owned tool-calling dialect with the new sigil-delimited format (`§` call header with inline `key=value` scalars, `«…»` verbatim body fence for the dominant string argument, `¤` reasoning, `‡‡` tool result) using single-token markers that never occur in source code. Verbatim fences escalate Markdown-style (`««…»»`) so re-rendered history never collides with payload content, and the scanner gates a bare `§` on an exact known-tool name to avoid swallowing prose. Round-trips and streams through the existing scanner contract at ~46% fewer tokens than the legacy format on typical calls; selectable via `tools.format` or `PI_DIALECT=pi`.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Updated `pi` dialect formatting to use a token-frugal, sigil-delimited format (`§`, `¤`, `‡‡`)
|
|
14
|
+
- Updated `pi` dialect body fences to automatically escalate when content contains fence markers
|
|
15
|
+
- Changed `pi` dialect tool results response format to `‡‡` blocks
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed Bedrock application inference profile ARNs to route requests to the ARN's region instead of the default Bedrock runtime region. ([#3004](https://github.com/can1357/oh-my-pi/issues/3004))
|
|
20
|
+
|
|
21
|
+
## [16.0.9] - 2026-06-18
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Fixed OAuth login replacing all other active accounts for the same provider, allowing multiple OAuth accounts to coexist concurrently.
|
|
26
|
+
- Fixed legacy `api_key` credentials not being replaced/disabled atomically upon upgrading to OAuth login.
|
|
27
|
+
- Fixed a logic issue where AuthStorage lost session-to-credential stickiness upon CLI restarts, causing cold-starts for server-side prompt cache (KV cache) and wasting tokens.
|
|
28
|
+
- Fixed GitHub Copilot Responses requests rejecting image inputs that carry the `detail: "original"` hint with an HTTP 400 by degrading the hint to `"auto"` for hosts that do not support it; other hosts still preserve native-resolution frames (snapcompact). ([#2822](https://github.com/can1357/oh-my-pi/issues/2822))
|
|
29
|
+
|
|
5
30
|
## [16.0.8] - 2026-06-18
|
|
6
31
|
|
|
7
32
|
### Fixed
|
|
@@ -341,11 +341,12 @@ export declare function repairOrphanResponsesToolOutputs(input: ResponseInput):
|
|
|
341
341
|
* {@link repairOrphanResponsesToolOutputs}.
|
|
342
342
|
*/
|
|
343
343
|
export declare function repairOrphanResponsesToolCalls(input: ResponseInput): ResponseInput;
|
|
344
|
-
export declare function convertResponsesInputContent(content: string | Array<TextContent | ImageContent>, supportsImages: boolean): ResponseInputContent[] | undefined;
|
|
344
|
+
export declare function convertResponsesInputContent(content: string | Array<TextContent | ImageContent>, supportsImages: boolean, supportsImageDetailOriginal: boolean): ResponseInputContent[] | undefined;
|
|
345
345
|
export interface BuildResponsesInputOptions<TApi extends Api> {
|
|
346
346
|
model: Model<TApi>;
|
|
347
347
|
context: Context;
|
|
348
348
|
strictResponsesPairing: boolean;
|
|
349
|
+
supportsImageDetailOriginal: boolean;
|
|
349
350
|
systemRole?: "system" | "developer";
|
|
350
351
|
nativeHistory?: {
|
|
351
352
|
replay: boolean;
|
|
@@ -357,7 +358,7 @@ export interface BuildResponsesInputOptions<TApi extends Api> {
|
|
|
357
358
|
}
|
|
358
359
|
export declare function buildResponsesInput<TApi extends Api>(options: BuildResponsesInputOptions<TApi>): ResponseInput;
|
|
359
360
|
export declare function convertResponsesAssistantMessage<TApi extends Api>(assistantMsg: AssistantMessage, model: Model<TApi>, msgIndex: number, knownCallIds: Set<string>, includeThinkingSignatures?: boolean, customCallIds?: Set<string>): ResponseInput;
|
|
360
|
-
export declare function appendResponsesToolResultMessages<TApi extends Api>(messages: ResponseInput, toolResult: ToolResultMessage, model: Model<TApi>, strictResponsesPairing: boolean, knownCallIds: ReadonlySet<string>, customCallIds?: ReadonlySet<string>): void;
|
|
361
|
+
export declare function appendResponsesToolResultMessages<TApi extends Api>(messages: ResponseInput, toolResult: ToolResultMessage, model: Model<TApi>, strictResponsesPairing: boolean, supportsImageDetailOriginal: boolean, knownCallIds: ReadonlySet<string>, customCallIds?: ReadonlySet<string>): void;
|
|
361
362
|
/**
|
|
362
363
|
* Per-block accumulation helpers shared by the two Responses decode loops —
|
|
363
364
|
* {@link processResponsesStream} (generic Responses) and the Codex stream
|
|
@@ -25,6 +25,12 @@ export interface OAuthProviderInfo {
|
|
|
25
25
|
id: OAuthProviderId;
|
|
26
26
|
name: string;
|
|
27
27
|
available: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Provider id the login stores credentials under, when it differs from `id`
|
|
30
|
+
* (e.g. `openai-codex-device` ⇒ `openai-codex`). Lets callers map a login
|
|
31
|
+
* entry back to the model provider it authenticates.
|
|
32
|
+
*/
|
|
33
|
+
storeCredentialsAs?: string;
|
|
28
34
|
}
|
|
29
35
|
export interface OAuthController {
|
|
30
36
|
onAuth?(info: OAuthAuthInfo): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "16.0.
|
|
4
|
+
"version": "16.0.10",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.0",
|
|
41
|
-
"@oh-my-pi/pi-catalog": "16.0.
|
|
42
|
-
"@oh-my-pi/pi-utils": "16.0.
|
|
41
|
+
"@oh-my-pi/pi-catalog": "16.0.10",
|
|
42
|
+
"@oh-my-pi/pi-utils": "16.0.10",
|
|
43
43
|
"arktype": "^2.2.0",
|
|
44
44
|
"partial-json": "^0.1.7",
|
|
45
45
|
"zod": "^4"
|
package/src/auth-storage.ts
CHANGED
|
@@ -1352,6 +1352,19 @@ export class AuthStorage {
|
|
|
1352
1352
|
const sessionMap = this.#sessionLastCredential.get(provider) ?? new Map();
|
|
1353
1353
|
sessionMap.set(sessionId, { type, index });
|
|
1354
1354
|
this.#sessionLastCredential.set(provider, sessionMap);
|
|
1355
|
+
|
|
1356
|
+
try {
|
|
1357
|
+
const credentialId = this.#getStoredCredentials(provider)[index]?.id;
|
|
1358
|
+
if (credentialId !== undefined) {
|
|
1359
|
+
const cacheKey = `session:sticky:${provider}:${sessionId}`;
|
|
1360
|
+
const cacheValue = JSON.stringify({ type, index, credentialId });
|
|
1361
|
+
// Expires in 30 days
|
|
1362
|
+
const expiresAtSec = Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60;
|
|
1363
|
+
this.#store.setCache(cacheKey, cacheValue, expiresAtSec);
|
|
1364
|
+
}
|
|
1365
|
+
} catch (err) {
|
|
1366
|
+
logger.debug("Failed to write session sticky credential to persistent store cache", { err });
|
|
1367
|
+
}
|
|
1355
1368
|
}
|
|
1356
1369
|
|
|
1357
1370
|
/** Retrieves the last credential used by a session. */
|
|
@@ -1360,17 +1373,59 @@ export class AuthStorage {
|
|
|
1360
1373
|
sessionId: string | undefined,
|
|
1361
1374
|
): { type: AuthCredential["type"]; index: number } | undefined {
|
|
1362
1375
|
if (!sessionId) return undefined;
|
|
1363
|
-
|
|
1376
|
+
let sessionMap = this.#sessionLastCredential.get(provider);
|
|
1377
|
+
if (sessionMap?.has(sessionId)) {
|
|
1378
|
+
return sessionMap.get(sessionId);
|
|
1379
|
+
}
|
|
1380
|
+
try {
|
|
1381
|
+
const cacheKey = `session:sticky:${provider}:${sessionId}`;
|
|
1382
|
+
const raw = this.#store.getCache(cacheKey);
|
|
1383
|
+
if (raw) {
|
|
1384
|
+
const val = JSON.parse(raw) as { type: AuthCredential["type"]; index: number; credentialId?: number };
|
|
1385
|
+
|
|
1386
|
+
if (val.credentialId !== undefined) {
|
|
1387
|
+
const stored = this.#getStoredCredentials(provider);
|
|
1388
|
+
const actualIndex = stored.findIndex(entry => entry.id === val.credentialId);
|
|
1389
|
+
if (actualIndex === -1 || stored[actualIndex]?.credential.type !== val.type) {
|
|
1390
|
+
this.#store.setCache(cacheKey, "", 0);
|
|
1391
|
+
return undefined;
|
|
1392
|
+
}
|
|
1393
|
+
val.index = actualIndex;
|
|
1394
|
+
} else {
|
|
1395
|
+
// Fallback: drop unsafe index-only cache rows to prevent wrong-account routing
|
|
1396
|
+
this.#store.setCache(cacheKey, "", 0);
|
|
1397
|
+
return undefined;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
if (!sessionMap) {
|
|
1401
|
+
sessionMap = new Map();
|
|
1402
|
+
this.#sessionLastCredential.set(provider, sessionMap);
|
|
1403
|
+
}
|
|
1404
|
+
const sessionVal = { type: val.type, index: val.index };
|
|
1405
|
+
sessionMap.set(sessionId, sessionVal);
|
|
1406
|
+
return sessionVal;
|
|
1407
|
+
}
|
|
1408
|
+
} catch (err) {
|
|
1409
|
+
logger.debug("Failed to read session sticky credential from persistent store cache", { err });
|
|
1410
|
+
}
|
|
1411
|
+
return undefined;
|
|
1364
1412
|
}
|
|
1365
1413
|
|
|
1366
1414
|
/** Clears the last credential used by a session for a provider. */
|
|
1367
1415
|
#clearSessionCredential(provider: string, sessionId: string | undefined): void {
|
|
1368
1416
|
if (!sessionId) return;
|
|
1369
1417
|
const sessionMap = this.#sessionLastCredential.get(provider);
|
|
1370
|
-
if (
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1418
|
+
if (sessionMap) {
|
|
1419
|
+
sessionMap.delete(sessionId);
|
|
1420
|
+
if (sessionMap.size === 0) {
|
|
1421
|
+
this.#sessionLastCredential.delete(provider);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
try {
|
|
1425
|
+
const cacheKey = `session:sticky:${provider}:${sessionId}`;
|
|
1426
|
+
this.#store.setCache(cacheKey, "", 0);
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
logger.debug("Failed to clear session sticky credential from persistent store cache", { err });
|
|
1374
1429
|
}
|
|
1375
1430
|
}
|
|
1376
1431
|
|
|
@@ -1549,6 +1604,17 @@ export class AuthStorage {
|
|
|
1549
1604
|
return rows;
|
|
1550
1605
|
}
|
|
1551
1606
|
|
|
1607
|
+
async #upsertOAuthCredential(provider: string, credential: OAuthCredential): Promise<void> {
|
|
1608
|
+
const stored = this.#store.upsertAuthCredentialRemote
|
|
1609
|
+
? await this.#store.upsertAuthCredentialRemote(provider, credential)
|
|
1610
|
+
: this.#store.upsertAuthCredentialForProvider(provider, credential);
|
|
1611
|
+
this.#setStoredCredentials(
|
|
1612
|
+
provider,
|
|
1613
|
+
stored.map(entry => ({ id: entry.id, credential: entry.credential })),
|
|
1614
|
+
);
|
|
1615
|
+
this.#resetProviderAssignments(provider);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1552
1618
|
/**
|
|
1553
1619
|
* Remove credential for a provider.
|
|
1554
1620
|
*/
|
|
@@ -1786,10 +1852,10 @@ export class AuthStorage {
|
|
|
1786
1852
|
return;
|
|
1787
1853
|
}
|
|
1788
1854
|
const newCredential: OAuthCredential = { type: "oauth", ...result };
|
|
1789
|
-
// Use
|
|
1790
|
-
//
|
|
1791
|
-
//
|
|
1792
|
-
await this
|
|
1855
|
+
// Use #upsertOAuthCredential to upsert the new credential.
|
|
1856
|
+
// Any legacy api_key rows from older versions will be cleaned up so they do not
|
|
1857
|
+
// shadow the new OAuth row, while preserving other active OAuth credentials.
|
|
1858
|
+
await this.#upsertOAuthCredential(def.storeCredentialsAs ?? provider, newCredential);
|
|
1793
1859
|
}
|
|
1794
1860
|
|
|
1795
1861
|
/**
|
|
@@ -4916,6 +4982,14 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
4916
4982
|
identityKey: resolveRowCredentialIdentityKey(providerName, row),
|
|
4917
4983
|
}));
|
|
4918
4984
|
|
|
4985
|
+
if (item.type === "oauth") {
|
|
4986
|
+
for (const row of existing) {
|
|
4987
|
+
if (row.credential && row.credential.type === "api_key") {
|
|
4988
|
+
this.#deleteStmt.run("replaced by oauth login", row.id);
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
}
|
|
4992
|
+
|
|
4919
4993
|
let targetId: number | null = null;
|
|
4920
4994
|
for (const row of existing) {
|
|
4921
4995
|
if (!matchesReplacementCredential(providerName, row.credential, row.identityKey, item)) continue;
|
|
@@ -19,7 +19,7 @@ const RESPONSE_OPEN_TOKENS: Record<Dialect, readonly string[]> = {
|
|
|
19
19
|
minimax: ["<function_results>", "<tool_response>"],
|
|
20
20
|
deepseek: ["<|tool▁outputs▁begin|>", "<|tool▁output▁begin|>"],
|
|
21
21
|
harmony: ["<|start|>functions."],
|
|
22
|
-
pi: ["
|
|
22
|
+
pi: ["‡‡"],
|
|
23
23
|
qwen3: ["<tool_response>"],
|
|
24
24
|
gemini: ["```tool_outputs"],
|
|
25
25
|
gemma: ["<|tool_response>"],
|
package/src/dialect/pi.md
CHANGED
|
@@ -1,57 +1,55 @@
|
|
|
1
1
|
## Format guide
|
|
2
2
|
|
|
3
|
-
A tool call
|
|
3
|
+
A tool call begins with `§` immediately followed by the function NAME (start each call on its own line). Scalar arguments follow on the same line as `key=value` pairs; a single large or multi-line string argument goes in a verbatim body fenced by `«…»` right after the header.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
<call:read path="src/a.ts" offset=50/>
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
Objects and arrays use child elements, repeating an element for each array item:
|
|
5
|
+
Scalar-only call (the line ends the call):
|
|
10
6
|
|
|
11
7
|
```text
|
|
12
|
-
|
|
13
|
-
<object>
|
|
14
|
-
<y>4</y>
|
|
15
|
-
<list>alpha</list>
|
|
16
|
-
<list>beta</list>
|
|
17
|
-
</object>
|
|
18
|
-
</call:configure>
|
|
8
|
+
§read path=src/a.ts offset=50 limit=200
|
|
19
9
|
```
|
|
20
10
|
|
|
21
|
-
|
|
11
|
+
Call with a verbatim body — everything between `«` and `»` is taken literally, no quoting or escaping:
|
|
22
12
|
|
|
23
13
|
```text
|
|
24
|
-
|
|
14
|
+
§edit path=src/server/auth.ts«
|
|
25
15
|
*** Begin Patch
|
|
26
|
-
|
|
16
|
+
*** Update File: src/server/auth.ts
|
|
17
|
+
@@ class AuthService
|
|
18
|
+
- login(user) {
|
|
19
|
+
+ async login(user, opts) {
|
|
27
20
|
*** End Patch
|
|
28
|
-
|
|
21
|
+
»
|
|
29
22
|
```
|
|
30
23
|
|
|
31
|
-
|
|
24
|
+
Argument values:
|
|
25
|
+
|
|
26
|
+
- Strings are written bare and verbatim (`path=src/a.ts`). Quote with `"…"` only when the value contains spaces or starts with `"`, `[`, or `{` (`_i="run the tests"`).
|
|
27
|
+
- Numbers, booleans, and `null` are JSON literals (`offset=50`, `force=true`).
|
|
28
|
+
- Arrays and objects are inline JSON (`paths=["src","test"]`).
|
|
29
|
+
- The body fence holds the call's first long/multi-line string parameter; its key is implied, never written.
|
|
30
|
+
|
|
31
|
+
Private reasoning goes in a `¤…¤` block before your calls:
|
|
32
32
|
|
|
33
33
|
```text
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
¤
|
|
35
|
+
brief reasoning
|
|
36
|
+
¤
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Tool results arrive in `‡‡…‡‡` blocks, read in call order:
|
|
40
40
|
|
|
41
41
|
```text
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
‡‡
|
|
43
|
+
verbatim tool result
|
|
44
|
+
‡‡
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
## Rules
|
|
48
48
|
|
|
49
|
-
- `NAME`
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
- Private reasoning goes in a `<thinking>…</thinking>` block before your calls; NEVER put calls inside it.
|
|
56
|
-
- Read each `<tool_response>` in call order. NEVER emit `<tool_response>` yourself.
|
|
49
|
+
- `NAME` MUST match a listed function; never wrap calls in JSON or fences.
|
|
50
|
+
- Put each scalar argument once as `key=value`; reserve the `«…»` body for the one dominant string argument (file contents, patches, commands, queries).
|
|
51
|
+
- Body text is verbatim — include no surrounding quotes. If the body itself contains `»`, widen BOTH guillemet fences equally (`««…»»`, `«««…»»»`).
|
|
52
|
+
- Emit parallel calls as consecutive `§…` blocks. NEVER invent call ids; results are positional.
|
|
53
|
+
- Private reasoning goes in a `¤…¤` block before your calls; NEVER put calls inside it, and keep a literal `¤` out of the reasoning text.
|
|
54
|
+
- Read each `‡‡…‡‡` result in call order. NEVER emit a `‡‡` block yourself.
|
|
57
55
|
- After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
|