@caupulican/pi-adaptative 0.81.12 → 0.81.15

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +24 -18
  3. package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +16 -10
  4. package/dist/cli/list-models.d.ts.map +1 -1
  5. package/dist/cli/list-models.js +5 -0
  6. package/dist/cli/list-models.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +30 -1
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +250 -21
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/model-registry.d.ts +1 -0
  12. package/dist/core/model-registry.d.ts.map +1 -1
  13. package/dist/core/model-registry.js +6 -0
  14. package/dist/core/model-registry.js.map +1 -1
  15. package/dist/core/models/adaptation-store.d.ts +18 -1
  16. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  17. package/dist/core/models/adaptation-store.js +31 -3
  18. package/dist/core/models/adaptation-store.js.map +1 -1
  19. package/dist/core/slash-commands.d.ts.map +1 -1
  20. package/dist/core/slash-commands.js +5 -0
  21. package/dist/core/slash-commands.js.map +1 -1
  22. package/dist/core/tool-repair-health.d.ts.map +1 -1
  23. package/dist/core/tool-repair-health.js +21 -1
  24. package/dist/core/tool-repair-health.js.map +1 -1
  25. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  26. package/dist/modes/interactive/interactive-mode.js +24 -0
  27. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  28. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  29. package/dist/modes/rpc/rpc-mode.js +8 -0
  30. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  31. package/dist/modes/rpc/rpc-types.d.ts +23 -1
  32. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  33. package/dist/modes/rpc/rpc-types.js.map +1 -1
  34. package/docs/models.md +5 -1
  35. package/docs/rpc.md +39 -0
  36. package/docs/settings.md +4 -2
  37. package/docs/tool-repair.md +15 -5
  38. package/docs/usage.md +1 -0
  39. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  40. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  41. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  42. package/examples/extensions/sandbox/package-lock.json +2 -2
  43. package/examples/extensions/sandbox/package.json +1 -1
  44. package/examples/extensions/with-deps/package-lock.json +2 -2
  45. package/examples/extensions/with-deps/package.json +1 -1
  46. package/npm-shrinkwrap.json +12 -12
  47. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,4 +1,17 @@
1
- ## [Unreleased]
1
+ ## [0.81.15] - 2026-07-07
2
+
3
+ ## [0.81.14] - 2026-07-07
4
+
5
+ ### Fixed
6
+ - Fixed text protocol model gating so persisted probe verdicts opt in only text-protocol models after env/settings/model gates, while unflagged native models do not receive the primer.
7
+ - Documented the `propertyCaseNormalize` and `jsonObjectPropertySalvage` repair modes in the operator docs and bundled tool-repair skill.
8
+
9
+ ## [0.81.13] - 2026-07-07
10
+
11
+ ### Fixed
12
+ - Fixed custom model registration to preserve `textToolCallProtocol` and show it in `--list-models`.
13
+ - Fixed text tool protocol calibration failures to persist, fail fast until explicit reset, and invalidate stale calibrated protocols after repeated live parse failures.
14
+ - Added `/toolprobe` and RPC `tool_probe` to live-probe native/text-protocol tool-call support and persist host-local verdicts.
2
15
 
3
16
  ## [0.81.12] - 2026-07-07
4
17
 
@@ -92,34 +92,40 @@ as code (not prose):
92
92
  grep, find).
93
93
  - **Failure grammar** (`references/failure-grammar.md`) — how a malformed
94
94
  call is recognized and repaired: the `errorSignature → transform → guard →
95
- note` table (modes 1–10, tool-agnostic) plus the tool-specific rows for
96
- bash (`command` as argv-array or object-wrapper, `timeout` as string/{}),
97
- edit, read/ls/grep/find. This is what `analyzer.ts` classifies against.
95
+ note` table (tool-agnostic modes, including the required-null bounce) plus
96
+ the tool-specific rows for bash (`command` as argv-array or object-wrapper,
97
+ `timeout` as string/{}), edit, read/ls/grep/find. This is what `analyzer.ts`
98
+ classifies against.
98
99
 
99
100
  ## The catalogue (summary; full table in references/failure-grammar.md)
100
101
 
101
102
  | # | Name | Model emits | Repair (guard-gated) |
102
103
  |---|---|---|---|
103
- | 1 | nullOptionalDrop | `null` for an optional field | delete key (required+null → bounce) |
104
- | 2 | jsonStringParse | `"[...]"`/`"{...}"` string where container expected | JSON.parse; keep if it matches + checks |
105
- | 3 | singleObjectWrap | single object where array-of-objects expected | wrap `[obj]` if it passes `items` |
106
- | 4 | bareScalarWrap | bare scalar where array expected | wrap `[v]` if it passes `items` |
107
- | 5 | emptyObjectPlaceholder | `{}` placeholder where scalar expected | delete if optional (default applies); else bounce |
108
- | 6 | numberFromString | `"42"` where number expected | `Number(s)` if finite |
109
- | 7 | boolFromString | `"true"`/`"false"` where bool expected | exact map (never truthiness) |
110
- | 8 | enumCaseNormalize | case/space enum variant | match to the one member, else bounce |
111
- | 9 | singleElementUnwrap | `[v]` where scalar expected | unwrap if 1 elem and checks |
112
- | 10 | stringifiedNumberInArray | `["1","2"]` where number[] expected | map Number if all finite |
104
+ | 1 | nullOptionalDrop | `null` for an optional field | delete key |
105
+ | 2 | nullRequiredBounce | `null` for a required field | no repair; bounce with required-value feedback |
106
+ | 3 | jsonStringParse | `"[...]"`/`"{...}"` string where container expected | JSON.parse; guarded smart-quote delimiter fallback; keep if it matches + checks |
107
+ | 4 | jsonObjectPropertySalvage | malformed object string with recoverable declared properties | keep declared property values if the whole object checks |
108
+ | 5 | singleObjectWrap | single object where array-of-objects expected | wrap `[obj]` if it passes `items` |
109
+ | 6 | bareScalarWrap | bare scalar where array expected | wrap `[v]` if it passes `items` |
110
+ | 7 | emptyObjectPlaceholder | `{}` placeholder where scalar expected | delete if optional (default applies); else bounce |
111
+ | 8 | numberFromString | `"42"` where number expected | `Number(s)` if finite |
112
+ | 9 | boolFromString | `"true"`/`"false"` where bool expected | exact map (never truthiness) |
113
+ | 10 | enumCaseNormalize | case/space enum variant | match to the one member, else bounce |
114
+ | 11 | propertyCaseNormalize | root argument key casing differs from schema casing | rename to the schema key when unique |
115
+ | 12 | singleElementUnwrap | `[v]` where scalar expected | unwrap if 1 elem and checks |
116
+ | 13 | stringifiedNumberInArray | `["1","2"]` where number[] expected | map Number if all finite |
117
+ | 14 | bashCommandArgvJoin | bash `command` sent as an argv list | join string values with spaces |
118
+ | 15 | bashCommandUnwrap | bash `command` sent as a single-key object wrapper | unwrap the string-valued wrapper |
113
119
 
114
- Plus bash-specific: `bashCommandArgvJoin` (argv list joined string) and
115
- `bashCommandUnwrap` (`{cmd}` → string). Every entry is a NAMED registry entry
120
+ Every entry is a NAMED registry entry
116
121
  `{name, errorSignature, transform, guard, noteTemplate}` — one table powers
117
122
  the repair, the teach note, the telemetry tag, and the docs row: the
118
123
  deterministic set of what pi can and will repair. Repairs NEVER invent values
119
124
  and are ALWAYS guard-gated (kept only if the transform Checks), so a repair
120
- can only ever turn an invalid call valid, never alter a valid one. Modes 6–10
121
- and the bash rows are the increment past the original four; they cost the hot
122
- path nothing because they run only on already-failed calls (see decision 2a).
125
+ can only ever turn an invalid call valid, never alter a valid one. The scalar,
126
+ array-number, and bash rows are the increment past the original four; they cost
127
+ the hot path nothing because they run only on already-failed calls (see decision
128
+ 2a).
123
129
 
124
130
  ## Method for an improvement pass
125
131
 
@@ -18,15 +18,17 @@ is in its parent's `required`; `def(P)` = P has a schema `default`.
18
18
  |---|---|---|---|---|---|---|
19
19
  | 1 | nullOptionalDrop | type mismatch at P | `null`, `!req(P)` | delete key P | always (absence is valid for optional) | "sent null for optional `P` → omit the field instead" |
20
20
  | 1b | nullRequiredBounce | type mismatch at P | `null`, `req(P)`, `!def(P)` | none | never (bounce) | "`P` is required and cannot be null → send a real value" |
21
- | 2 | jsonStringParse | expect array\|object at P, got string | `"[...]"`/`"{...}"` | `JSON.parse(s)` | parsed matches expect(P) AND sub-checks | "sent `P` as a quoted JSON string → send a raw JSON array/object" |
21
+ | 2 | jsonStringParse | expect array\|object at P, got string | `"[...]"`/`"{...}"`, including text-protocol smart-quote delimiter drift | `JSON.parse(s)`; if strict parse fails, normalize `“`/`”` delimiters and the observed missing object-key quote pattern, then parse | parsed matches expect(P) AND sub-checks | "sent `P` as a quoted JSON string → send a raw JSON array/object" |
22
+ | 2b | jsonObjectPropertySalvage | expect object at P, got malformed object string | e.g. `{"path":"x" extra:1000}` | extract schema-declared JSON literal property values into a new object | each declared property appears at most once, no undeclared value is kept, and whole-object Check passes | "sent `P` as malformed JSON with recoverable declared properties → keep the schema-declared properties" |
22
23
  | 3 | singleObjectWrap | expect array at P, got object | `{...}` | `[obj]` | `[obj]` passes `items(P)` | "sent one object where `P` takes a list → wrap it in `[ ]`" |
23
24
  | 4 | bareScalarWrap | expect array at P, got scalar | string/number/bool | `[v]` | `[v]` passes `items(P)` | "sent a single value where `P` takes a list → wrap it in `[ ]`" |
24
25
  | 5 | emptyObjectPlaceholder | expect scalar at P (or array whose `items` reject `{}`), got object | `{}` | delete key P | `!req(P)` (schema default applies) else bounce | "sent `{}` as a placeholder → omit `P`; its default applies" |
25
26
  | 6 | numberFromString | expect number/integer at P, got string | `"42"`, numeric | `Number(s)` | finite (and integer if integer(P)) | "sent `P` as a quoted number → send a bare number" |
26
27
  | 7 | boolFromString | expect boolean at P, got string | `"true"`/`"false"` | `s === "true"` | exact match only (never truthiness) | "sent `P` as a quoted boolean → send bare true/false" |
27
28
  | 8 | enumCaseNormalize | expect enum at P, got string not in set | case/space variant | match case-insensitively/trimmed to one enum member | exactly one member matches | "`P` must be one of `a|b|c` → matched `<value>`" |
28
- | 9 | singleElementUnwrap | expect scalar at P, got 1-elem array | `[v]` | `v` | `v` passes expect(P) AND `length===1` | "sent `P` as a 1-item list where a single value was expectedsend the value" |
29
- | 10 | stringifiedNumberInArray | expect number[] at P, got string[] | `["1","2"]` | map `Number` | all finite | "list `P` holds quoted numbers → send bare numbers" |
29
+ | 9 | propertyCaseNormalize | root object has key K whose casing differs from a declared schema property | e.g. `Path` for `path` | rename K to the schema key | exactly one declared root property matches case-insensitively and the canonical key is absent | "sent `P` with different property-key casing use the schema key casing" |
30
+ | 10 | singleElementUnwrap | expect scalar at P, got 1-elem array | `[v]` | `v` | `v` passes expect(P) AND `length===1` | "sent `P` as a 1-item list where a single value was expected → send the value" |
31
+ | 11 | stringifiedNumberInArray | expect number[] at P, got string[] | `["1","2"]` | map `Number` | all finite | "list `P` holds quoted numbers → send bare numbers" |
30
32
 
31
33
  Rules that keep this deterministic and safe:
32
34
  - **Repairs never invent a value.** 5 relies on the schema `default`; nothing
@@ -34,13 +36,14 @@ Rules that keep this deterministic and safe:
34
36
  - **Guard is mandatory.** Every transform runs on a clone and is kept ONLY if
35
37
  it Checks against the sub-schema at P. A transform whose guard fails leaves
36
38
  args untouched and falls through to the next applicable mode, then bounce.
37
- - **Order within one path:** 2 (parse) → 8/6/7 (coerce scalar) → 3/4/10 (wrap)
38
- 9 (unwrap) → 5 (placeholder-drop) → 1 (null-drop). Parse before wrap so a
39
+ - **Order within one path:** 2 (strict/quote-normalized parse) → 2b (declared-property salvage) → 8/6/7 (coerce scalar) → 3/4/11 (wrap)
40
+ 10 (unwrap) → 5 (placeholder-drop) → 1 (null-drop). Parse before wrap so a
39
41
  stringified array becomes an array, not a wrapped string. Across paths:
40
- instance-path order.
42
+ instance-path order. Root property-key casing repair runs before these per-path
43
+ transforms because required-property validator errors do not carry the misspelled key path.
41
44
  - **One re-Check.** After all per-path transforms, Check the whole args once.
42
45
  Pass → return repaired; fail → bounce. Never loop transforms.
43
- - Modes 6–10 are the increment past the original four; each is guard-gated so
46
+ - Modes 2b and 6–11 are the increment past the original four; each is guard-gated so
44
47
  it can only ever turn an invalid call into a valid one, never change a
45
48
  valid call (the hot path never reaches them — see Performance).
46
49
 
@@ -104,10 +107,13 @@ The failure grammar only ever runs on ALREADY-FAILED calls. The hot path
104
107
  `validator.Check(args)` (cached compiled validator) and returns the SAME
105
108
  object — no clone, no walk, no grammar lookup. This is the ~99% path for
106
109
  strong models and it is unchanged from today's cost.
107
- 2. **Repairs are O(errors), not O(schema).** The analyzer walks the
110
+ 2. **Repairs are bounded on the failed path.** The analyzer walks the
108
111
  validator's error list (already produced by the failed Check), not the
109
- schema tree. Each error maps to at most a few candidate modes by a static
110
- dispatch table keyed on `(expect, got)` a Map lookup, not a scan.
112
+ schema tree; the exceptions are `propertyCaseNormalize` and
113
+ `jsonObjectPropertySalvage`, which scan only root object keys or declared
114
+ object properties after a failed Check. Each
115
+ path error maps to at most a few candidate modes by a static dispatch table
116
+ keyed on `(expect, got)` — a Map lookup, not a scan.
111
117
  3. **No per-call compilation.** errorSignature matchers are precompiled
112
118
  constants; note templates are format strings; the registry is built once
113
119
  at module load. Nothing in the repair path constructs a RegExp, compiles a
@@ -1 +1 @@
1
- {"version":3,"file":"list-models.d.ts","sourceRoot":"","sources":["../../src/cli/list-models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAiB/D;;GAEG;AACH,wBAAsB,UAAU,CAAC,aAAa,EAAE,aAAa,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkFpG","sourcesContent":["/**\n * List available models with optional fuzzy search\n */\n\nimport type { Api, Model } from \"@caupulican/pi-ai\";\nimport { fuzzyFilter } from \"@caupulican/pi-tui\";\nimport chalk from \"chalk\";\nimport { formatNoModelsAvailableMessage } from \"../core/auth-guidance.ts\";\nimport type { ModelRegistry } from \"../core/model-registry.ts\";\n\n/**\n * Format a number as human-readable (e.g., 200000 -> \"200K\", 1000000 -> \"1M\")\n */\nfunction formatTokenCount(count: number): string {\n\tif (count >= 1_000_000) {\n\t\tconst millions = count / 1_000_000;\n\t\treturn millions % 1 === 0 ? `${millions}M` : `${millions.toFixed(1)}M`;\n\t}\n\tif (count >= 1_000) {\n\t\tconst thousands = count / 1_000;\n\t\treturn thousands % 1 === 0 ? `${thousands}K` : `${thousands.toFixed(1)}K`;\n\t}\n\treturn count.toString();\n}\n\n/**\n * List available models, optionally filtered by search pattern\n */\nexport async function listModels(modelRegistry: ModelRegistry, searchPattern?: string): Promise<void> {\n\tconst loadError = modelRegistry.getError();\n\tif (loadError) {\n\t\tconsole.error(chalk.yellow(`Warning: errors loading models.json:\\n${loadError}`));\n\t}\n\n\tconst models = modelRegistry.getAvailable();\n\n\tif (models.length === 0) {\n\t\tconsole.log(formatNoModelsAvailableMessage());\n\t\treturn;\n\t}\n\n\t// Apply fuzzy filter if search pattern provided\n\tlet filteredModels: Model<Api>[] = models;\n\tif (searchPattern) {\n\t\tfilteredModels = fuzzyFilter(models, searchPattern, (m) => `${m.provider} ${m.id}`);\n\t}\n\n\tif (filteredModels.length === 0) {\n\t\tconsole.log(`No models matching \"${searchPattern}\"`);\n\t\treturn;\n\t}\n\n\t// Sort by provider, then by model id\n\tfilteredModels.sort((a, b) => {\n\t\tconst providerCmp = a.provider.localeCompare(b.provider);\n\t\tif (providerCmp !== 0) return providerCmp;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n\n\t// Calculate column widths\n\tconst rows = filteredModels.map((m) => ({\n\t\tprovider: m.provider,\n\t\tmodel: m.id,\n\t\tcontext: formatTokenCount(m.contextWindow),\n\t\tmaxOut: formatTokenCount(m.maxTokens),\n\t\tthinking: m.reasoning ? \"yes\" : \"no\",\n\t\timages: m.input.includes(\"image\") ? \"yes\" : \"no\",\n\t}));\n\n\tconst headers = {\n\t\tprovider: \"provider\",\n\t\tmodel: \"model\",\n\t\tcontext: \"context\",\n\t\tmaxOut: \"max-out\",\n\t\tthinking: \"thinking\",\n\t\timages: \"images\",\n\t};\n\n\tconst widths = {\n\t\tprovider: Math.max(headers.provider.length, ...rows.map((r) => r.provider.length)),\n\t\tmodel: Math.max(headers.model.length, ...rows.map((r) => r.model.length)),\n\t\tcontext: Math.max(headers.context.length, ...rows.map((r) => r.context.length)),\n\t\tmaxOut: Math.max(headers.maxOut.length, ...rows.map((r) => r.maxOut.length)),\n\t\tthinking: Math.max(headers.thinking.length, ...rows.map((r) => r.thinking.length)),\n\t\timages: Math.max(headers.images.length, ...rows.map((r) => r.images.length)),\n\t};\n\n\t// Print header\n\tconst headerLine = [\n\t\theaders.provider.padEnd(widths.provider),\n\t\theaders.model.padEnd(widths.model),\n\t\theaders.context.padEnd(widths.context),\n\t\theaders.maxOut.padEnd(widths.maxOut),\n\t\theaders.thinking.padEnd(widths.thinking),\n\t\theaders.images.padEnd(widths.images),\n\t].join(\" \");\n\tconsole.log(headerLine);\n\n\t// Print rows\n\tfor (const row of rows) {\n\t\tconst line = [\n\t\t\trow.provider.padEnd(widths.provider),\n\t\t\trow.model.padEnd(widths.model),\n\t\t\trow.context.padEnd(widths.context),\n\t\t\trow.maxOut.padEnd(widths.maxOut),\n\t\t\trow.thinking.padEnd(widths.thinking),\n\t\t\trow.images.padEnd(widths.images),\n\t\t].join(\" \");\n\t\tconsole.log(line);\n\t}\n}\n"]}
1
+ {"version":3,"file":"list-models.d.ts","sourceRoot":"","sources":["../../src/cli/list-models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAiB/D;;GAEG;AACH,wBAAsB,UAAU,CAAC,aAAa,EAAE,aAAa,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFpG","sourcesContent":["/**\n * List available models with optional fuzzy search\n */\n\nimport type { Api, Model } from \"@caupulican/pi-ai\";\nimport { fuzzyFilter } from \"@caupulican/pi-tui\";\nimport chalk from \"chalk\";\nimport { formatNoModelsAvailableMessage } from \"../core/auth-guidance.ts\";\nimport type { ModelRegistry } from \"../core/model-registry.ts\";\n\n/**\n * Format a number as human-readable (e.g., 200000 -> \"200K\", 1000000 -> \"1M\")\n */\nfunction formatTokenCount(count: number): string {\n\tif (count >= 1_000_000) {\n\t\tconst millions = count / 1_000_000;\n\t\treturn millions % 1 === 0 ? `${millions}M` : `${millions.toFixed(1)}M`;\n\t}\n\tif (count >= 1_000) {\n\t\tconst thousands = count / 1_000;\n\t\treturn thousands % 1 === 0 ? `${thousands}K` : `${thousands.toFixed(1)}K`;\n\t}\n\treturn count.toString();\n}\n\n/**\n * List available models, optionally filtered by search pattern\n */\nexport async function listModels(modelRegistry: ModelRegistry, searchPattern?: string): Promise<void> {\n\tconst loadError = modelRegistry.getError();\n\tif (loadError) {\n\t\tconsole.error(chalk.yellow(`Warning: errors loading models.json:\\n${loadError}`));\n\t}\n\n\tconst models = modelRegistry.getAvailable();\n\n\tif (models.length === 0) {\n\t\tconsole.log(formatNoModelsAvailableMessage());\n\t\treturn;\n\t}\n\n\t// Apply fuzzy filter if search pattern provided\n\tlet filteredModels: Model<Api>[] = models;\n\tif (searchPattern) {\n\t\tfilteredModels = fuzzyFilter(models, searchPattern, (m) => `${m.provider} ${m.id}`);\n\t}\n\n\tif (filteredModels.length === 0) {\n\t\tconsole.log(`No models matching \"${searchPattern}\"`);\n\t\treturn;\n\t}\n\n\t// Sort by provider, then by model id\n\tfilteredModels.sort((a, b) => {\n\t\tconst providerCmp = a.provider.localeCompare(b.provider);\n\t\tif (providerCmp !== 0) return providerCmp;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n\n\t// Calculate column widths\n\tconst rows = filteredModels.map((m) => ({\n\t\tprovider: m.provider,\n\t\tmodel: m.id,\n\t\tcontext: formatTokenCount(m.contextWindow),\n\t\tmaxOut: formatTokenCount(m.maxTokens),\n\t\tthinking: m.reasoning ? \"yes\" : \"no\",\n\t\timages: m.input.includes(\"image\") ? \"yes\" : \"no\",\n\t\ttextTools: m.textToolCallProtocol ? \"yes\" : \"no\",\n\t}));\n\n\tconst headers = {\n\t\tprovider: \"provider\",\n\t\tmodel: \"model\",\n\t\tcontext: \"context\",\n\t\tmaxOut: \"max-out\",\n\t\tthinking: \"thinking\",\n\t\timages: \"images\",\n\t\ttextTools: \"text-tools\",\n\t};\n\n\tconst widths = {\n\t\tprovider: Math.max(headers.provider.length, ...rows.map((r) => r.provider.length)),\n\t\tmodel: Math.max(headers.model.length, ...rows.map((r) => r.model.length)),\n\t\tcontext: Math.max(headers.context.length, ...rows.map((r) => r.context.length)),\n\t\tmaxOut: Math.max(headers.maxOut.length, ...rows.map((r) => r.maxOut.length)),\n\t\tthinking: Math.max(headers.thinking.length, ...rows.map((r) => r.thinking.length)),\n\t\timages: Math.max(headers.images.length, ...rows.map((r) => r.images.length)),\n\t\ttextTools: Math.max(headers.textTools.length, ...rows.map((r) => r.textTools.length)),\n\t};\n\n\t// Print header\n\tconst headerLine = [\n\t\theaders.provider.padEnd(widths.provider),\n\t\theaders.model.padEnd(widths.model),\n\t\theaders.context.padEnd(widths.context),\n\t\theaders.maxOut.padEnd(widths.maxOut),\n\t\theaders.thinking.padEnd(widths.thinking),\n\t\theaders.images.padEnd(widths.images),\n\t\theaders.textTools.padEnd(widths.textTools),\n\t].join(\" \");\n\tconsole.log(headerLine);\n\n\t// Print rows\n\tfor (const row of rows) {\n\t\tconst line = [\n\t\t\trow.provider.padEnd(widths.provider),\n\t\t\trow.model.padEnd(widths.model),\n\t\t\trow.context.padEnd(widths.context),\n\t\t\trow.maxOut.padEnd(widths.maxOut),\n\t\t\trow.thinking.padEnd(widths.thinking),\n\t\t\trow.images.padEnd(widths.images),\n\t\t\trow.textTools.padEnd(widths.textTools),\n\t\t].join(\" \");\n\t\tconsole.log(line);\n\t}\n}\n"]}
@@ -55,6 +55,7 @@ export async function listModels(modelRegistry, searchPattern) {
55
55
  maxOut: formatTokenCount(m.maxTokens),
56
56
  thinking: m.reasoning ? "yes" : "no",
57
57
  images: m.input.includes("image") ? "yes" : "no",
58
+ textTools: m.textToolCallProtocol ? "yes" : "no",
58
59
  }));
59
60
  const headers = {
60
61
  provider: "provider",
@@ -63,6 +64,7 @@ export async function listModels(modelRegistry, searchPattern) {
63
64
  maxOut: "max-out",
64
65
  thinking: "thinking",
65
66
  images: "images",
67
+ textTools: "text-tools",
66
68
  };
67
69
  const widths = {
68
70
  provider: Math.max(headers.provider.length, ...rows.map((r) => r.provider.length)),
@@ -71,6 +73,7 @@ export async function listModels(modelRegistry, searchPattern) {
71
73
  maxOut: Math.max(headers.maxOut.length, ...rows.map((r) => r.maxOut.length)),
72
74
  thinking: Math.max(headers.thinking.length, ...rows.map((r) => r.thinking.length)),
73
75
  images: Math.max(headers.images.length, ...rows.map((r) => r.images.length)),
76
+ textTools: Math.max(headers.textTools.length, ...rows.map((r) => r.textTools.length)),
74
77
  };
75
78
  // Print header
76
79
  const headerLine = [
@@ -80,6 +83,7 @@ export async function listModels(modelRegistry, searchPattern) {
80
83
  headers.maxOut.padEnd(widths.maxOut),
81
84
  headers.thinking.padEnd(widths.thinking),
82
85
  headers.images.padEnd(widths.images),
86
+ headers.textTools.padEnd(widths.textTools),
83
87
  ].join(" ");
84
88
  console.log(headerLine);
85
89
  // Print rows
@@ -91,6 +95,7 @@ export async function listModels(modelRegistry, searchPattern) {
91
95
  row.maxOut.padEnd(widths.maxOut),
92
96
  row.thinking.padEnd(widths.thinking),
93
97
  row.images.padEnd(widths.images),
98
+ row.textTools.padEnd(widths.textTools),
94
99
  ].join(" ");
95
100
  console.log(line);
96
101
  }
@@ -1 +1 @@
1
- {"version":3,"file":"list-models.js","sourceRoot":"","sources":["../../src/cli/list-models.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * List available models with optional fuzzy search\n */\n\nimport type { Api, Model } from \"@caupulican/pi-ai\";\nimport { fuzzyFilter } from \"@caupulican/pi-tui\";\nimport chalk from \"chalk\";\nimport { formatNoModelsAvailableMessage } from \"../core/auth-guidance.ts\";\nimport type { ModelRegistry } from \"../core/model-registry.ts\";\n\n/**\n * Format a number as human-readable (e.g., 200000 -> \"200K\", 1000000 -> \"1M\")\n */\nfunction formatTokenCount(count: number): string {\n\tif (count >= 1_000_000) {\n\t\tconst millions = count / 1_000_000;\n\t\treturn millions % 1 === 0 ? `${millions}M` : `${millions.toFixed(1)}M`;\n\t}\n\tif (count >= 1_000) {\n\t\tconst thousands = count / 1_000;\n\t\treturn thousands % 1 === 0 ? `${thousands}K` : `${thousands.toFixed(1)}K`;\n\t}\n\treturn count.toString();\n}\n\n/**\n * List available models, optionally filtered by search pattern\n */\nexport async function listModels(modelRegistry: ModelRegistry, searchPattern?: string): Promise<void> {\n\tconst loadError = modelRegistry.getError();\n\tif (loadError) {\n\t\tconsole.error(chalk.yellow(`Warning: errors loading models.json:\\n${loadError}`));\n\t}\n\n\tconst models = modelRegistry.getAvailable();\n\n\tif (models.length === 0) {\n\t\tconsole.log(formatNoModelsAvailableMessage());\n\t\treturn;\n\t}\n\n\t// Apply fuzzy filter if search pattern provided\n\tlet filteredModels: Model<Api>[] = models;\n\tif (searchPattern) {\n\t\tfilteredModels = fuzzyFilter(models, searchPattern, (m) => `${m.provider} ${m.id}`);\n\t}\n\n\tif (filteredModels.length === 0) {\n\t\tconsole.log(`No models matching \"${searchPattern}\"`);\n\t\treturn;\n\t}\n\n\t// Sort by provider, then by model id\n\tfilteredModels.sort((a, b) => {\n\t\tconst providerCmp = a.provider.localeCompare(b.provider);\n\t\tif (providerCmp !== 0) return providerCmp;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n\n\t// Calculate column widths\n\tconst rows = filteredModels.map((m) => ({\n\t\tprovider: m.provider,\n\t\tmodel: m.id,\n\t\tcontext: formatTokenCount(m.contextWindow),\n\t\tmaxOut: formatTokenCount(m.maxTokens),\n\t\tthinking: m.reasoning ? \"yes\" : \"no\",\n\t\timages: m.input.includes(\"image\") ? \"yes\" : \"no\",\n\t}));\n\n\tconst headers = {\n\t\tprovider: \"provider\",\n\t\tmodel: \"model\",\n\t\tcontext: \"context\",\n\t\tmaxOut: \"max-out\",\n\t\tthinking: \"thinking\",\n\t\timages: \"images\",\n\t};\n\n\tconst widths = {\n\t\tprovider: Math.max(headers.provider.length, ...rows.map((r) => r.provider.length)),\n\t\tmodel: Math.max(headers.model.length, ...rows.map((r) => r.model.length)),\n\t\tcontext: Math.max(headers.context.length, ...rows.map((r) => r.context.length)),\n\t\tmaxOut: Math.max(headers.maxOut.length, ...rows.map((r) => r.maxOut.length)),\n\t\tthinking: Math.max(headers.thinking.length, ...rows.map((r) => r.thinking.length)),\n\t\timages: Math.max(headers.images.length, ...rows.map((r) => r.images.length)),\n\t};\n\n\t// Print header\n\tconst headerLine = [\n\t\theaders.provider.padEnd(widths.provider),\n\t\theaders.model.padEnd(widths.model),\n\t\theaders.context.padEnd(widths.context),\n\t\theaders.maxOut.padEnd(widths.maxOut),\n\t\theaders.thinking.padEnd(widths.thinking),\n\t\theaders.images.padEnd(widths.images),\n\t].join(\" \");\n\tconsole.log(headerLine);\n\n\t// Print rows\n\tfor (const row of rows) {\n\t\tconst line = [\n\t\t\trow.provider.padEnd(widths.provider),\n\t\t\trow.model.padEnd(widths.model),\n\t\t\trow.context.padEnd(widths.context),\n\t\t\trow.maxOut.padEnd(widths.maxOut),\n\t\t\trow.thinking.padEnd(widths.thinking),\n\t\t\trow.images.padEnd(widths.images),\n\t\t].join(\" \");\n\t\tconsole.log(line);\n\t}\n}\n"]}
1
+ {"version":3,"file":"list-models.js","sourceRoot":"","sources":["../../src/cli/list-models.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * List available models with optional fuzzy search\n */\n\nimport type { Api, Model } from \"@caupulican/pi-ai\";\nimport { fuzzyFilter } from \"@caupulican/pi-tui\";\nimport chalk from \"chalk\";\nimport { formatNoModelsAvailableMessage } from \"../core/auth-guidance.ts\";\nimport type { ModelRegistry } from \"../core/model-registry.ts\";\n\n/**\n * Format a number as human-readable (e.g., 200000 -> \"200K\", 1000000 -> \"1M\")\n */\nfunction formatTokenCount(count: number): string {\n\tif (count >= 1_000_000) {\n\t\tconst millions = count / 1_000_000;\n\t\treturn millions % 1 === 0 ? `${millions}M` : `${millions.toFixed(1)}M`;\n\t}\n\tif (count >= 1_000) {\n\t\tconst thousands = count / 1_000;\n\t\treturn thousands % 1 === 0 ? `${thousands}K` : `${thousands.toFixed(1)}K`;\n\t}\n\treturn count.toString();\n}\n\n/**\n * List available models, optionally filtered by search pattern\n */\nexport async function listModels(modelRegistry: ModelRegistry, searchPattern?: string): Promise<void> {\n\tconst loadError = modelRegistry.getError();\n\tif (loadError) {\n\t\tconsole.error(chalk.yellow(`Warning: errors loading models.json:\\n${loadError}`));\n\t}\n\n\tconst models = modelRegistry.getAvailable();\n\n\tif (models.length === 0) {\n\t\tconsole.log(formatNoModelsAvailableMessage());\n\t\treturn;\n\t}\n\n\t// Apply fuzzy filter if search pattern provided\n\tlet filteredModels: Model<Api>[] = models;\n\tif (searchPattern) {\n\t\tfilteredModels = fuzzyFilter(models, searchPattern, (m) => `${m.provider} ${m.id}`);\n\t}\n\n\tif (filteredModels.length === 0) {\n\t\tconsole.log(`No models matching \"${searchPattern}\"`);\n\t\treturn;\n\t}\n\n\t// Sort by provider, then by model id\n\tfilteredModels.sort((a, b) => {\n\t\tconst providerCmp = a.provider.localeCompare(b.provider);\n\t\tif (providerCmp !== 0) return providerCmp;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n\n\t// Calculate column widths\n\tconst rows = filteredModels.map((m) => ({\n\t\tprovider: m.provider,\n\t\tmodel: m.id,\n\t\tcontext: formatTokenCount(m.contextWindow),\n\t\tmaxOut: formatTokenCount(m.maxTokens),\n\t\tthinking: m.reasoning ? \"yes\" : \"no\",\n\t\timages: m.input.includes(\"image\") ? \"yes\" : \"no\",\n\t\ttextTools: m.textToolCallProtocol ? \"yes\" : \"no\",\n\t}));\n\n\tconst headers = {\n\t\tprovider: \"provider\",\n\t\tmodel: \"model\",\n\t\tcontext: \"context\",\n\t\tmaxOut: \"max-out\",\n\t\tthinking: \"thinking\",\n\t\timages: \"images\",\n\t\ttextTools: \"text-tools\",\n\t};\n\n\tconst widths = {\n\t\tprovider: Math.max(headers.provider.length, ...rows.map((r) => r.provider.length)),\n\t\tmodel: Math.max(headers.model.length, ...rows.map((r) => r.model.length)),\n\t\tcontext: Math.max(headers.context.length, ...rows.map((r) => r.context.length)),\n\t\tmaxOut: Math.max(headers.maxOut.length, ...rows.map((r) => r.maxOut.length)),\n\t\tthinking: Math.max(headers.thinking.length, ...rows.map((r) => r.thinking.length)),\n\t\timages: Math.max(headers.images.length, ...rows.map((r) => r.images.length)),\n\t\ttextTools: Math.max(headers.textTools.length, ...rows.map((r) => r.textTools.length)),\n\t};\n\n\t// Print header\n\tconst headerLine = [\n\t\theaders.provider.padEnd(widths.provider),\n\t\theaders.model.padEnd(widths.model),\n\t\theaders.context.padEnd(widths.context),\n\t\theaders.maxOut.padEnd(widths.maxOut),\n\t\theaders.thinking.padEnd(widths.thinking),\n\t\theaders.images.padEnd(widths.images),\n\t\theaders.textTools.padEnd(widths.textTools),\n\t].join(\" \");\n\tconsole.log(headerLine);\n\n\t// Print rows\n\tfor (const row of rows) {\n\t\tconst line = [\n\t\t\trow.provider.padEnd(widths.provider),\n\t\t\trow.model.padEnd(widths.model),\n\t\t\trow.context.padEnd(widths.context),\n\t\t\trow.maxOut.padEnd(widths.maxOut),\n\t\t\trow.thinking.padEnd(widths.thinking),\n\t\t\trow.images.padEnd(widths.images),\n\t\t\trow.textTools.padEnd(widths.textTools),\n\t\t].join(\" \");\n\t\tconsole.log(line);\n\t}\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, StreamIdleOptions, ThinkingLevel } from "@caupulican/pi-agent-core";
2
2
  import { type CustomMessage } from "@caupulican/pi-agent-core";
3
3
  import { type BranchSummaryEntry, type CompactionResult, type SessionManager } from "@caupulican/pi-agent-core/node";
4
- import type { CacheRetention, ImageContent, Message, Model, StopReason, TextContent, Usage } from "@caupulican/pi-ai";
4
+ import type { CacheRetention, ImageContent, Message, Model, StopReason, TextContent, TextToolProtocolVariant, Usage } from "@caupulican/pi-ai";
5
5
  import type { CapabilityEnvelope, EvidenceBundle, LearningDecision, WorkerRequest, WorkerResult } from "./autonomy/contracts.ts";
6
6
  import type { LaneRecord } from "./autonomy/lane-tracker.ts";
7
7
  import type { AutonomyDiagnosticSnapshot, AutonomyStatusSnapshot, GateOutcomeHistoryEntry } from "./autonomy/status.ts";
@@ -198,6 +198,17 @@ export interface PromptOptions {
198
198
  /** Whether an idle active goal should auto-inject bounded continuation prompts after this prompt settles. Default: true. */
199
199
  autoContinueGoal?: boolean;
200
200
  }
201
+ export type ToolProbeVerdict = "native" | "text-protocol" | "none";
202
+ export interface ToolProbeResult {
203
+ model: string;
204
+ verdict: ToolProbeVerdict;
205
+ variant?: TextToolProtocolVariant;
206
+ diagnostic?: string;
207
+ }
208
+ export interface ToolProbeReport {
209
+ results: ToolProbeResult[];
210
+ table: string;
211
+ }
201
212
  /** Result from cycleModel() */
202
213
  export interface ModelCycleResult {
203
214
  model: Model<any>;
@@ -361,6 +372,9 @@ export declare class AgentSession {
361
372
  private readonly _localRuntimeController;
362
373
  private readonly _modelAdaptationStore;
363
374
  private readonly _repairModeSessionCounts;
375
+ private readonly _textProtocolParseFailures;
376
+ private _textProtocolParseObservedThisTurn;
377
+ private _textProtocolValidationOutcomeThisTurn;
364
378
  /** Assembles the session's base system prompt from live session state (see
365
379
  * system-prompt-builder.ts); owns the paired _baseSystemPromptOptions. */
366
380
  private readonly _systemPromptBuilder;
@@ -492,9 +506,23 @@ export declare class AgentSession {
492
506
  private _applyToolRepairLayerSettings;
493
507
  private _getModelAdaptationRulesForPrompt;
494
508
  private _textProtocolFlag;
509
+ private _streamForToolProbe;
495
510
  private _textProtocolCalibrationContext;
511
+ private _messageHasEchoProbe;
512
+ private _runNativeToolProbeTrial;
496
513
  private _runTextProtocolTrial;
514
+ private _calibrateTextToolProtocolForModel;
497
515
  private _ensureTextToolProtocolForActiveModel;
516
+ private _modelRef;
517
+ private _formatToolProbeReport;
518
+ private _storeToolProbe;
519
+ private _probeToolCallingForModel;
520
+ private _resolveToolProbeModels;
521
+ probeToolCalling(target?: string): Promise<ToolProbeReport>;
522
+ private _handleTextToolProtocolParse;
523
+ private _handleTextToolProtocolValidationOutcome;
524
+ private _recordTextToolProtocolParseOutcomeFromLastAssistant;
525
+ private _looksLikeTextToolProtocolAttempt;
498
526
  private _recordToolValidationBounce;
499
527
  private _tagModelAdaptationRuleTeaching;
500
528
  private _repairSessionCount;
@@ -552,6 +580,7 @@ export declare class AgentSession {
552
580
  formatContextCompositionDashboard(): string;
553
581
  formatToolRepairHealthReport(): string;
554
582
  removeToolRepairRule(model: string, mode: string): boolean;
583
+ resetToolProtocolCalibration(model: string): boolean;
555
584
  /** Curation status for diagnostics/dashboard: settings, live telemetry, last refusal reason. */
556
585
  /** Curation status for diagnostics/dashboard (delegates to {@link ContextPipeline.getContextCurationStatus}). */
557
586
  getContextCurationStatus(): {