@caupulican/pi-adaptative 0.81.15 → 0.81.16

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 (52) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/bundled-resources/runtimes/hf-transformers-openai-server.py +427 -0
  3. package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +14 -7
  4. package/dist/core/agent-session.d.ts +9 -3
  5. package/dist/core/agent-session.d.ts.map +1 -1
  6. package/dist/core/agent-session.js +92 -16
  7. package/dist/core/agent-session.js.map +1 -1
  8. package/dist/core/local-runtime-controller.d.ts +17 -9
  9. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  10. package/dist/core/local-runtime-controller.js +124 -20
  11. package/dist/core/local-runtime-controller.js.map +1 -1
  12. package/dist/core/models/adaptation-store.d.ts +2 -0
  13. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  14. package/dist/core/models/adaptation-store.js +4 -0
  15. package/dist/core/models/adaptation-store.js.map +1 -1
  16. package/dist/core/models/default-model-suggestions.d.ts +4 -4
  17. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  18. package/dist/core/models/default-model-suggestions.js +9 -0
  19. package/dist/core/models/default-model-suggestions.js.map +1 -1
  20. package/dist/core/models/local-registration.d.ts +12 -0
  21. package/dist/core/models/local-registration.d.ts.map +1 -1
  22. package/dist/core/models/local-registration.js +68 -0
  23. package/dist/core/models/local-registration.js.map +1 -1
  24. package/dist/core/models/local-runtime.d.ts +77 -1
  25. package/dist/core/models/local-runtime.d.ts.map +1 -1
  26. package/dist/core/models/local-runtime.js +295 -4
  27. package/dist/core/models/local-runtime.js.map +1 -1
  28. package/dist/core/models/model-ref.d.ts +4 -0
  29. package/dist/core/models/model-ref.d.ts.map +1 -1
  30. package/dist/core/models/model-ref.js +12 -3
  31. package/dist/core/models/model-ref.js.map +1 -1
  32. package/dist/core/tool-repair-health.d.ts.map +1 -1
  33. package/dist/core/tool-repair-health.js +2 -1
  34. package/dist/core/tool-repair-health.js.map +1 -1
  35. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  36. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  37. package/dist/modes/interactive/interactive-mode.js +4 -0
  38. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  39. package/dist/modes/interactive/local-model-commands.d.ts +3 -1
  40. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  41. package/dist/modes/interactive/local-model-commands.js +133 -19
  42. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  43. package/docs/models.md +19 -1
  44. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  45. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  46. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  47. package/examples/extensions/sandbox/package-lock.json +2 -2
  48. package/examples/extensions/sandbox/package.json +1 -1
  49. package/examples/extensions/with-deps/package-lock.json +2 -2
  50. package/examples/extensions/with-deps/package.json +1 -1
  51. package/npm-shrinkwrap.json +12 -12
  52. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.81.16] - 2026-07-08
2
+
3
+ ### Added
4
+ - Added graded `/toolprobe` native capability reporting and persistence so native verdicts require both an echo probe and a task-scale read probe, while echo-only or absent native support can calibrate the text protocol.
5
+ - Added MiniCPM5-1B to the live acceptance fleet as a full-base native tool-calling target through the pi-managed Transformers sidecar.
6
+ - Added a pi-managed Hugging Face Transformers runtime path for curated full-base local model suggestions, starting with `openbmb/MiniCPM5-1B`, using an isolated venv and pi-owned HF cache.
7
+
8
+ ### Fixed
9
+ - Fixed tool probing so a real task-scale native tool call wins without requiring a separate echo-only probe, and the MiniCPM sidecar stops generation after a complete native function call.
10
+ - Documented the Ollama serving-context requirement for local OpenAI-compatible models.
11
+
1
12
  ## [0.81.15] - 2026-07-07
2
13
 
3
14
  ## [0.81.14] - 2026-07-07
@@ -0,0 +1,427 @@
1
+ #!/usr/bin/env python3
2
+ """Tiny OpenAI-compatible chat sidecar for pi-managed Hugging Face Transformers models.
3
+
4
+ Intentionally stdlib-only at the server layer: the managed venv supplies torch,
5
+ transformers, and huggingface_hub, while HTTP serving stays dependency-free so pi
6
+ does not need FastAPI/Uvicorn just to run a suggested local model.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import html
13
+ import json
14
+ import os
15
+ import re
16
+ import sys
17
+ import time
18
+ import traceback
19
+ import uuid
20
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
21
+ from typing import Any
22
+
23
+ _MODEL_ID = ""
24
+ _TOKENIZER: Any = None
25
+ _MODEL: Any = None
26
+ _TORCH: Any = None
27
+ _DEVICE = "cpu"
28
+ _FUNCTION_CALL_RE = re.compile(r"<function\b[^>]*>.*?</function>", re.DOTALL)
29
+ _FUNCTION_NAME_RE = re.compile(r"<function\b[^>]*\bname=(['\"])(.*?)\1", re.DOTALL)
30
+ _PARAM_RE = re.compile(r"<param\b[^>]*\bname=(['\"])(.*?)\1[^>]*>(.*?)</param>", re.DOTALL)
31
+
32
+
33
+ def _json_response(handler: BaseHTTPRequestHandler, status: int, body: dict[str, Any]) -> None:
34
+ payload = json.dumps(body).encode("utf-8")
35
+ handler.send_response(status)
36
+ handler.send_header("content-type", "application/json")
37
+ handler.send_header("content-length", str(len(payload)))
38
+ handler.end_headers()
39
+ handler.wfile.write(payload)
40
+
41
+
42
+ def _message_text(content: Any) -> str:
43
+ if isinstance(content, str):
44
+ return content
45
+ if isinstance(content, list):
46
+ parts: list[str] = []
47
+ for item in content:
48
+ if isinstance(item, dict):
49
+ text = item.get("text")
50
+ if isinstance(text, str):
51
+ parts.append(text)
52
+ return "\n".join(parts)
53
+ return "" if content is None else str(content)
54
+
55
+
56
+ def _decode_arguments(arguments: Any) -> Any:
57
+ if isinstance(arguments, str):
58
+ try:
59
+ return json.loads(arguments)
60
+ except json.JSONDecodeError as error:
61
+ raise ValueError(f"tool_call function.arguments must be valid JSON: {error.msg}") from error
62
+ return arguments
63
+
64
+
65
+ def _normalize_tool_calls(tool_calls: Any) -> list[dict[str, Any]]:
66
+ normalized: list[dict[str, Any]] = []
67
+ if not isinstance(tool_calls, list):
68
+ return normalized
69
+ for tool_call in tool_calls:
70
+ if not isinstance(tool_call, dict):
71
+ continue
72
+ function = tool_call.get("function")
73
+ if not isinstance(function, dict):
74
+ continue
75
+ name = function.get("name")
76
+ if not isinstance(name, str) or not name:
77
+ continue
78
+ normalized_call: dict[str, Any] = {
79
+ "type": "function",
80
+ "function": {"name": name, "arguments": _decode_arguments(function.get("arguments"))},
81
+ }
82
+ tool_call_id = tool_call.get("id")
83
+ if isinstance(tool_call_id, str) and tool_call_id:
84
+ normalized_call["id"] = tool_call_id
85
+ normalized.append(normalized_call)
86
+ return normalized
87
+
88
+
89
+ def _normalize_messages(messages: Any) -> list[dict[str, Any]]:
90
+ normalized: list[dict[str, Any]] = []
91
+ if not isinstance(messages, list):
92
+ return normalized
93
+ for message in messages:
94
+ if not isinstance(message, dict):
95
+ continue
96
+ role = message.get("role")
97
+ if role == "developer":
98
+ role = "system"
99
+ if role not in {"system", "user", "assistant", "tool"}:
100
+ role = "user"
101
+ normalized_message: dict[str, Any] = {"role": str(role), "content": _message_text(message.get("content"))}
102
+ if role == "assistant":
103
+ tool_calls = _normalize_tool_calls(message.get("tool_calls"))
104
+ if tool_calls:
105
+ normalized_message["tool_calls"] = tool_calls
106
+ if role == "tool":
107
+ tool_call_id = message.get("tool_call_id")
108
+ if isinstance(tool_call_id, str) and tool_call_id:
109
+ normalized_message["tool_call_id"] = tool_call_id
110
+ normalized.append(normalized_message)
111
+ return normalized
112
+
113
+
114
+ def _render_prompt(messages: list[dict[str, Any]], tools: Any) -> str:
115
+ tokenizer = _TOKENIZER
116
+ kwargs: dict[str, Any] = {
117
+ "tokenize": False,
118
+ "add_generation_prompt": True,
119
+ }
120
+ if isinstance(tools, list) and tools:
121
+ kwargs["tools"] = tools
122
+ return tokenizer.apply_chat_template(messages, **kwargs)
123
+
124
+
125
+ def _generation_options(request: dict[str, Any]) -> dict[str, Any]:
126
+ max_tokens = request.get("max_completion_tokens", request.get("max_tokens", 512))
127
+ try:
128
+ max_new_tokens = max(1, min(int(max_tokens), 2048))
129
+ except Exception:
130
+ max_new_tokens = 512
131
+
132
+ temperature_value = request.get("temperature", 0.0)
133
+ try:
134
+ temperature = float(temperature_value)
135
+ except Exception:
136
+ temperature = 0.0
137
+ do_sample = temperature > 0.0
138
+
139
+ return {
140
+ "max_new_tokens": max_new_tokens,
141
+ "do_sample": do_sample,
142
+ **({"temperature": temperature} if do_sample else {}),
143
+ }
144
+
145
+
146
+ def _clean_generated_text(text: str) -> str:
147
+ tokenizer = _TOKENIZER
148
+ eos = getattr(tokenizer, "eos_token", None)
149
+ if eos:
150
+ text = text.replace(eos, "")
151
+ pad = getattr(tokenizer, "pad_token", None)
152
+ if pad and pad != eos:
153
+ text = text.replace(pad, "")
154
+ return text.strip()
155
+
156
+
157
+ def _decode_param_value(raw: str) -> Any:
158
+ stripped = raw.strip()
159
+ if stripped.startswith("{") or stripped.startswith("["):
160
+ try:
161
+ return json.loads(stripped)
162
+ except Exception:
163
+ return stripped
164
+ return stripped
165
+
166
+
167
+ def _extract_native_function_calls(text: str) -> tuple[str, list[dict[str, Any]]]:
168
+ calls: list[dict[str, Any]] = []
169
+ spans: list[tuple[int, int]] = []
170
+ for match in _FUNCTION_CALL_RE.finditer(text):
171
+ block = match.group(0)
172
+ name_match = _FUNCTION_NAME_RE.search(block)
173
+ if not name_match:
174
+ continue
175
+ name = html.unescape(name_match.group(2).strip())
176
+ if not name:
177
+ continue
178
+ arguments: dict[str, Any] = {}
179
+ for param_match in _PARAM_RE.finditer(block):
180
+ param_name = html.unescape(param_match.group(2).strip())
181
+ if not param_name:
182
+ continue
183
+ arguments[param_name] = _decode_param_value(html.unescape(param_match.group(3)))
184
+ calls.append(
185
+ {
186
+ "id": f"call_{uuid.uuid4().hex}",
187
+ "type": "function",
188
+ "function": {"name": name, "arguments": json.dumps(arguments, ensure_ascii=False)},
189
+ }
190
+ )
191
+ spans.append(match.span())
192
+ if not calls:
193
+ return text, []
194
+ remaining_parts: list[str] = []
195
+ cursor = 0
196
+ for start, end in spans:
197
+ remaining_parts.append(text[cursor:start])
198
+ cursor = end
199
+ remaining_parts.append(text[cursor:])
200
+ return "".join(remaining_parts).strip(), calls
201
+
202
+
203
+ def _function_call_stopping_criteria(prompt_token_count: int) -> Any:
204
+ from transformers import StoppingCriteria, StoppingCriteriaList
205
+
206
+ class StopAfterFunctionCall(StoppingCriteria):
207
+ def __call__(self, input_ids: Any, _scores: Any, **_kwargs: Any) -> bool:
208
+ generated_ids = input_ids[0][prompt_token_count:]
209
+ if int(generated_ids.shape[-1]) == 0:
210
+ return False
211
+ text = _TOKENIZER.decode(generated_ids, skip_special_tokens=False)
212
+ return bool(_FUNCTION_CALL_RE.search(text))
213
+
214
+ return StoppingCriteriaList([StopAfterFunctionCall()])
215
+
216
+
217
+ def _generate(request: dict[str, Any]) -> str:
218
+ tokenizer = _TOKENIZER
219
+ model = _MODEL
220
+ torch = _TORCH
221
+ messages = _normalize_messages(request.get("messages"))
222
+ prompt = _render_prompt(messages, request.get("tools"))
223
+ encoded = tokenizer(prompt, return_tensors="pt")
224
+ encoded = {key: value.to(_DEVICE) for key, value in encoded.items()}
225
+ input_length = int(encoded["input_ids"].shape[-1])
226
+ options = _generation_options(request)
227
+ eos_token_id = getattr(tokenizer, "eos_token_id", None)
228
+ pad_token_id = getattr(tokenizer, "pad_token_id", None) or eos_token_id
229
+ generation_args: dict[str, Any] = {
230
+ **encoded,
231
+ "eos_token_id": eos_token_id,
232
+ "pad_token_id": pad_token_id,
233
+ **options,
234
+ }
235
+ if isinstance(request.get("tools"), list) and request.get("tools"):
236
+ generation_args["stopping_criteria"] = _function_call_stopping_criteria(input_length)
237
+ with torch.inference_mode():
238
+ output = model.generate(**generation_args)
239
+ generated = output[0][input_length:]
240
+ return _clean_generated_text(tokenizer.decode(generated, skip_special_tokens=False))
241
+
242
+
243
+ def _stream_chunk(handler: BaseHTTPRequestHandler, chunk: dict[str, Any]) -> None:
244
+ handler.wfile.write(f"data: {json.dumps(chunk)}\n\n".encode("utf-8"))
245
+ handler.wfile.flush()
246
+
247
+
248
+ class Handler(BaseHTTPRequestHandler):
249
+ server_version = "pi-hf-transformers/1"
250
+
251
+ def log_message(self, fmt: str, *args: Any) -> None:
252
+ sys.stderr.write("[%s] %s\n" % (self.log_date_time_string(), fmt % args))
253
+
254
+ def do_GET(self) -> None: # noqa: N802 - stdlib handler API
255
+ if self.path == "/health":
256
+ _json_response(self, 200, {"ok": True, "model": _MODEL_ID})
257
+ return
258
+ if self.path == "/v1/models":
259
+ _json_response(
260
+ self,
261
+ 200,
262
+ {"object": "list", "data": [{"id": _MODEL_ID, "object": "model", "owned_by": "pi"}]},
263
+ )
264
+ return
265
+ _json_response(self, 404, {"error": "not found"})
266
+
267
+ def do_POST(self) -> None: # noqa: N802 - stdlib handler API
268
+ if self.path != "/v1/chat/completions":
269
+ _json_response(self, 404, {"error": "not found"})
270
+ return
271
+ try:
272
+ length = int(self.headers.get("content-length", "0"))
273
+ request = json.loads(self.rfile.read(length).decode("utf-8"))
274
+ text = _generate(request)
275
+ content, tool_calls = _extract_native_function_calls(text)
276
+ if request.get("stream", False):
277
+ self.send_response(200)
278
+ self.send_header("content-type", "text/event-stream")
279
+ self.send_header("cache-control", "no-cache")
280
+ self.end_headers()
281
+ chunk_id = f"chatcmpl-pi-{uuid.uuid4().hex}"
282
+ created = int(time.time())
283
+ _stream_chunk(
284
+ self,
285
+ {
286
+ "id": chunk_id,
287
+ "object": "chat.completion.chunk",
288
+ "created": created,
289
+ "model": _MODEL_ID,
290
+ "choices": [{"index": 0, "delta": {"role": "assistant"}, "finish_reason": None}],
291
+ },
292
+ )
293
+ if content:
294
+ _stream_chunk(
295
+ self,
296
+ {
297
+ "id": chunk_id,
298
+ "object": "chat.completion.chunk",
299
+ "created": created,
300
+ "model": _MODEL_ID,
301
+ "choices": [{"index": 0, "delta": {"content": content}, "finish_reason": None}],
302
+ },
303
+ )
304
+ for index, tool_call in enumerate(tool_calls):
305
+ _stream_chunk(
306
+ self,
307
+ {
308
+ "id": chunk_id,
309
+ "object": "chat.completion.chunk",
310
+ "created": created,
311
+ "model": _MODEL_ID,
312
+ "choices": [
313
+ {
314
+ "index": 0,
315
+ "delta": {
316
+ "tool_calls": [
317
+ {
318
+ "index": index,
319
+ "id": tool_call["id"],
320
+ "type": "function",
321
+ "function": tool_call["function"],
322
+ }
323
+ ]
324
+ },
325
+ "finish_reason": None,
326
+ }
327
+ ],
328
+ },
329
+ )
330
+ _stream_chunk(
331
+ self,
332
+ {
333
+ "id": chunk_id,
334
+ "object": "chat.completion.chunk",
335
+ "created": created,
336
+ "model": _MODEL_ID,
337
+ "choices": [
338
+ {"index": 0, "delta": {}, "finish_reason": "tool_calls" if tool_calls else "stop"}
339
+ ],
340
+ },
341
+ )
342
+ self.wfile.write(b"data: [DONE]\n\n")
343
+ self.wfile.flush()
344
+ return
345
+
346
+ _json_response(
347
+ self,
348
+ 200,
349
+ {
350
+ "id": f"chatcmpl-pi-{uuid.uuid4().hex}",
351
+ "object": "chat.completion",
352
+ "created": int(time.time()),
353
+ "model": _MODEL_ID,
354
+ "choices": [
355
+ {
356
+ "index": 0,
357
+ "message": {
358
+ "role": "assistant",
359
+ "content": content if content else None,
360
+ **({"tool_calls": tool_calls} if tool_calls else {}),
361
+ },
362
+ "finish_reason": "tool_calls" if tool_calls else "stop",
363
+ }
364
+ ],
365
+ },
366
+ )
367
+ except Exception as exc: # pragma: no cover - surfaced to the TypeScript caller as HTTP 500
368
+ traceback.print_exc()
369
+ _json_response(self, 500, {"error": str(exc)})
370
+
371
+
372
+ def _download_only(model_id: str, cache_dir: str) -> None:
373
+ from huggingface_hub import snapshot_download
374
+
375
+ snapshot_download(repo_id=model_id, cache_dir=cache_dir)
376
+
377
+
378
+ def _load_model(model_id: str, cache_dir: str, device: str) -> None:
379
+ global _MODEL_ID, _TOKENIZER, _MODEL, _TORCH, _DEVICE
380
+
381
+ import torch
382
+ from transformers import AutoModelForCausalLM, AutoTokenizer
383
+
384
+ threads = os.environ.get("PI_TRANSFORMERS_THREADS")
385
+ if threads:
386
+ try:
387
+ torch.set_num_threads(max(1, int(threads)))
388
+ except Exception:
389
+ pass
390
+
391
+ dtype_name = os.environ.get("PI_TRANSFORMERS_TORCH_DTYPE", "float32")
392
+ torch_dtype = getattr(torch, dtype_name, torch.float32)
393
+ tokenizer = AutoTokenizer.from_pretrained(model_id, cache_dir=cache_dir)
394
+ model = AutoModelForCausalLM.from_pretrained(model_id, cache_dir=cache_dir, torch_dtype=torch_dtype)
395
+ model.to(device)
396
+ model.eval()
397
+
398
+ _MODEL_ID = model_id
399
+ _TOKENIZER = tokenizer
400
+ _MODEL = model
401
+ _TORCH = torch
402
+ _DEVICE = device
403
+
404
+
405
+ def main() -> None:
406
+ parser = argparse.ArgumentParser()
407
+ parser.add_argument("--model-id", required=True)
408
+ parser.add_argument("--host", default="127.0.0.1")
409
+ parser.add_argument("--port", type=int, default=18100)
410
+ parser.add_argument("--cache-dir", required=True)
411
+ parser.add_argument("--device", default=os.environ.get("PI_TRANSFORMERS_DEVICE", "cpu"))
412
+ parser.add_argument("--download-only", action="store_true")
413
+ args = parser.parse_args()
414
+
415
+ os.makedirs(args.cache_dir, exist_ok=True)
416
+ if args.download_only:
417
+ _download_only(args.model_id, args.cache_dir)
418
+ return
419
+
420
+ _load_model(args.model_id, args.cache_dir, args.device)
421
+ server = ThreadingHTTPServer((args.host, args.port), Handler)
422
+ print(f"pi hf-transformers server ready model={args.model_id} url=http://{args.host}:{args.port}", flush=True)
423
+ server.serve_forever()
424
+
425
+
426
+ if __name__ == "__main__":
427
+ main()
@@ -54,11 +54,18 @@ pre-trained on other stacks' conventions.
54
54
  2. `<tool_call>{"name":"X","arguments":{...}}</tool_call>` (common OSS
55
55
  convention; `arguments` may itself be a JSON STRING — that is exactly
56
56
  R31 mode 2, handed straight to the repairer).
57
- 3. Fenced block tagged `tool`/`tool_call`:
57
+ 3. Fenced block tagged `tool`/`tool_call`/`json`:
58
58
  ```` ```tool\n{"name":"X","arguments":{...}}\n``` ````
59
+ 4. XML function-call convention:
60
+ `<function name="X"><param name="k">value</param></function>`.
61
+ The scanner requires an explicit `</function>` close tag. Each `param`
62
+ becomes a string-valued argument; R31 owns string-to-number, string-to-bool,
63
+ and JSON-string coercion after parsing. Nested `<function>` bodies,
64
+ duplicate param names, or non-whitespace text inside the function body are
65
+ ambiguous and are refused rather than guessed.
59
66
  Everything else is prose. Ambiguous text is NEVER guessed into a call
60
- (doctrine: no heuristic soup). Unknown `tool-name` → not a call; it becomes a
61
- bounce with the "unknown tool" note listing valid names.
67
+ (doctrine: no heuristic soup). Unknown `tool-name` → a text-protocol bounce with
68
+ an "unknown tool" note listing valid names.
62
69
 
63
70
  ## 3. Schema -> primer projection (how the dictionary is generated)
64
71
 
@@ -135,7 +142,7 @@ string }` where:
135
142
 
136
143
  The trial tool is `echo(data:string)` (harness-provided, side-effect-free).
137
144
  Calibration asks the model to `echo` a known token via the protocol and
138
- checks the parser round-trips it. Grammar variants tried on failure, in
139
- order: (a) restate header + one example; (b) drop to fenced-block variant 3
140
- if angle-bracket tags fail; (c) shrink the dictionary to the single tool
141
- under test. The variant that first round-trips is persisted per model (R46).
145
+ checks the parser round-trips it. Grammar variants tried on failure are the
146
+ parser's supported variants in deterministic order: canonical `<pi:call>`,
147
+ `<tool_call>`, fenced JSON/tool blocks, then XML `<function>`. The variant that
148
+ first round-trips is persisted per model (R46).
@@ -28,8 +28,9 @@ import { type CurationProposals } from "./learning/skill-curator.ts";
28
28
  import type { MemoryProvider } from "./memory/memory-provider.ts";
29
29
  import { type ModelCapabilityProfile } from "./model-capability.ts";
30
30
  import type { ModelRegistry } from "./model-registry.ts";
31
+ import { type NativeToolProbeGrade } from "./models/adaptation-store.ts";
31
32
  import type { StoredFitnessReport } from "./models/fitness-store.ts";
32
- import type { LocalRuntimeDeps, OllamaRuntime } from "./models/local-runtime.ts";
33
+ import type { LocalRuntimeDeps, OllamaRuntime, TransformersRuntime } from "./models/local-runtime.ts";
33
34
  import { type PromptTemplate } from "./prompt-templates.ts";
34
35
  import type { ModelFitnessReport } from "./research/model-fitness.ts";
35
36
  import type { ResearchRunResult } from "./research/research-runner.ts";
@@ -203,6 +204,7 @@ export interface ToolProbeResult {
203
204
  model: string;
204
205
  verdict: ToolProbeVerdict;
205
206
  variant?: TextToolProtocolVariant;
207
+ nativeGrade?: NativeToolProbeGrade;
206
208
  diagnostic?: string;
207
209
  }
208
210
  export interface ToolProbeReport {
@@ -508,8 +510,11 @@ export declare class AgentSession {
508
510
  private _textProtocolFlag;
509
511
  private _streamForToolProbe;
510
512
  private _textProtocolCalibrationContext;
511
- private _messageHasEchoProbe;
512
- private _runNativeToolProbeTrial;
513
+ private _messageHasToolCallWithStringArgument;
514
+ private _nativeToolProbeSystemPrompt;
515
+ private _runNativeReadTaskProbeTrial;
516
+ private _runNativeEchoToolProbeTrial;
517
+ private _gradeNativeToolCallingForModel;
513
518
  private _runTextProtocolTrial;
514
519
  private _calibrateTextToolProtocolForModel;
515
520
  private _ensureTextToolProtocolForActiveModel;
@@ -766,6 +771,7 @@ export declare class AgentSession {
766
771
  * untracked child. Delegates to {@link LocalRuntimeController}.
767
772
  */
768
773
  getLocalRuntime(baseUrl?: string): OllamaRuntime;
774
+ getTransformersRuntime(modelId: string, baseUrl?: string): TransformersRuntime;
769
775
  /** models.json registers a local model's baseUrl as `<server>/v1` (OpenAI-compat); the runtime's
770
776
  * own health/boot endpoints are on the Ollama-native server root. Delegates to
771
777
  * {@link LocalRuntimeController}; kept here for `_warnIfManualModelChoiceIsRisky`'s own use. */