mlx-bun 0.4.0 → 1.0.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 +1453 -88
- package/THIRD_PARTY_NOTICES.md +882 -0
- package/bin/mlx-bun.convert.mjs +4 -0
- package/bin/mlx-bun.fuse.mjs +4 -0
- package/bin/mlx-bun.generate.mjs +4 -0
- package/bin/mlx-bun.lora.mjs +4 -0
- package/bin/mlx-bun.mjs +13 -21
- package/bin/mlx-bun.server.mjs +4 -0
- package/bin/mlx-bun.upload.mjs +4 -0
- package/dist/web/app.js +9584 -0
- package/package.json +50 -53
- package/src/cli/ane-install.ts +76 -0
- package/src/cli/args.ts +179 -0
- package/src/cli/engine-entry.ts +68 -0
- package/src/cli/inference.ts +214 -0
- package/src/cli/job-entry.ts +97 -0
- package/src/cli/library-host.ts +117 -0
- package/src/cli/main.ts +57 -0
- package/src/cli/memory-engine.ts +163 -0
- package/src/cli/memory.ts +36 -0
- package/src/cli/mlx-lm-aliases.ts +309 -0
- package/src/cli/model-choice.ts +33 -0
- package/src/cli/model-selection.ts +84 -0
- package/src/cli/module-host.ts +11 -0
- package/src/cli/module-verbs.ts +50 -0
- package/src/cli/numerical-policy.ts +12 -0
- package/src/cli/perplexity.ts +89 -0
- package/src/cli/serve-host.ts +423 -0
- package/src/cli/serve-isolated.ts +217 -0
- package/src/cli/serve-options.ts +106 -0
- package/src/cli/serve-state.ts +205 -0
- package/src/cli/serve.ts +500 -0
- package/src/cli/served-model-host.ts +102 -0
- package/src/cli/server-entry.ts +148 -0
- package/src/cli/serving-unit.ts +177 -0
- package/src/{tui.ts → cli/terminal.ts} +13 -8
- package/src/cli/worker-adapters.ts +34 -0
- package/src/cli/worker-entry.ts +272 -0
- package/src/cli/worker-unit.ts +179 -0
- package/src/engine/adapter-operation.ts +26 -0
- package/src/engine/cache-services.ts +114 -0
- package/src/engine/completion.ts +39 -0
- package/src/engine/generation-gateway.ts +358 -0
- package/src/engine/index.ts +59 -0
- package/src/engine/kv-budget.ts +120 -0
- package/src/engine/media-preparation.ts +232 -0
- package/src/engine/model-binding.ts +24 -0
- package/src/engine/model-host.ts +513 -0
- package/src/engine/model-serving.ts +89 -0
- package/src/{serve → engine}/preparation.ts +10 -8
- package/src/{serve → engine}/session-completion-engine.ts +6 -6
- package/src/engine/telemetry.ts +109 -0
- package/src/hub/downloads.ts +116 -0
- package/src/jobs/db.ts +16 -7
- package/src/jobs/events.ts +29 -0
- package/src/jobs/executable.ts +5 -0
- package/src/jobs/host.ts +51 -0
- package/src/jobs/in-process.ts +55 -0
- package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
- package/src/jobs/runner.ts +150 -231
- package/src/jobs/service.ts +75 -0
- package/src/jobs/sse.ts +59 -23
- package/src/jobs/worker-memory.ts +46 -0
- package/src/jobs/worker-process.ts +163 -0
- package/src/jobs/worker-supervisor.ts +258 -0
- package/src/modules.ts +58 -0
- package/src/publishing/catalog-hub.ts +18 -0
- package/src/publishing/credentials.ts +31 -0
- package/src/publishing/upload.ts +37 -0
- package/src/residency/leased-adapters.ts +21 -0
- package/src/residency/local-records.ts +16 -0
- package/src/residency/model-residency.ts +405 -0
- package/src/residency/record-index.ts +28 -0
- package/src/residency/resident-estimate.ts +50 -0
- package/src/{anthropic.ts → server/anthropic.ts} +3 -81
- package/src/server/cache-routes.ts +36 -0
- package/src/{serve → server}/chat-request.ts +12 -10
- package/src/{serve → server}/chat-stage.ts +27 -22
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +42 -42
- package/src/server/decision-body.ts +55 -0
- package/src/server/decision-routes.ts +134 -0
- package/src/server/decision-wire.ts +228 -0
- package/src/{serve → server}/discovery-routes.ts +42 -83
- package/src/server/generated-token-history.ts +57 -0
- package/src/{serve → server}/http.ts +25 -13
- package/src/{serve → server}/inference-request.ts +5 -5
- package/src/server/job-routes.ts +20 -0
- package/src/server/media-prompt.ts +63 -0
- package/src/server/memory-completion-client.ts +46 -0
- package/src/server/model-routes.ts +192 -0
- package/src/{serve → server}/openai-wire.ts +3 -2
- package/src/{serve → server}/pipeline.ts +1 -1
- package/src/server/prompt-builder.ts +13 -0
- package/src/server/prompt-contracts.ts +17 -0
- package/src/server/proxy-routes.ts +187 -0
- package/src/server/publishing-routes.ts +41 -0
- package/src/{serve → server}/request-plan.ts +8 -8
- package/src/{serve → server}/request-prep.ts +82 -68
- package/src/{serve → server}/responses-client.ts +30 -13
- package/src/{responses.ts → server/responses.ts} +12 -93
- package/src/server/routes.ts +205 -0
- package/src/server/start.ts +72 -0
- package/src/server/status-routes.ts +108 -0
- package/src/{serve → server}/text-completion-stage.ts +25 -4
- package/src/server/text-prompt.ts +12 -0
- package/src/{serve → server}/token-streams.ts +36 -38
- package/src/server/worker-routes.ts +311 -0
- package/src/storage/paths.ts +61 -0
- package/src/web/assets.ts +37 -0
- package/src/web/{src → browser}/api.ts +5 -23
- package/src/web/browser/chat-host.ts +17 -0
- package/src/web/browser/installed-panels.ts +6 -0
- package/src/web/browser/main.ts +55 -0
- package/src/web/browser/markdown.ts +279 -0
- package/src/web/browser/memory-panel.ts +9 -0
- package/src/web/browser/palette.ts +64 -0
- package/src/web/browser/protocol.ts +13 -0
- package/src/web/browser/shell.ts +467 -0
- package/src/web/{src → browser}/status.ts +32 -36
- package/src/web/build.ts +51 -0
- package/src/web/public/app.html +1054 -0
- package/src/web/{sw.js → public/sw.js} +4 -3
- package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
- package/src/web/{vendor → public/vendor}/hljs.js +2 -2
- package/THIRD_PARTY_LICENSES.md +0 -51
- package/docs/README.md +0 -94
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
- package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
- package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
- package/docs/archive/investigations/curve-bisector-routing.md +0 -123
- package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
- package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
- package/docs/archive/investigations/dspark-handoff.md +0 -224
- package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
- package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
- package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
- package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
- package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
- package/docs/archive/investigations/lab-build-journal.md +0 -157
- package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
- package/docs/archive/investigations/openjarvis-fit.md +0 -198
- package/docs/archive/investigations/openwiki-evaluation.md +0 -249
- package/docs/archive/investigations/optimization_plan.md +0 -346
- package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
- package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
- package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
- package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
- package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
- package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
- package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
- package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
- package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
- package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
- package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
- package/docs/archive/planning/chat-ui-vision.md +0 -50
- package/docs/archive/planning/chunk-task-roadmap.md +0 -77
- package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
- package/docs/archive/planning/journal.md +0 -316
- package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
- package/docs/archive/planning/pi-integration-review.md +0 -178
- package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
- package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
- package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
- package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
- package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
- package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
- package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
- package/docs/archive/planning/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -1841
- package/docs/design/decode-speed-program.md +0 -2349
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -705
- package/docs/design/kv-cache.md +0 -824
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -1990
- package/docs/design/turboquant.md +0 -2212
- package/docs/design/unified-engine-frontier-plan.md +0 -1880
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -594
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/release-notes-v0.4.0.md +0 -45
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3613
- package/docs/reference/cli.md +0 -585
- package/docs/reference/distribution.md +0 -355
- package/docs/reference/environment.md +0 -398
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -674
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -334
- package/docs/reference/server-api.md +0 -1775
- package/docs/reference/server-config.md +0 -846
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/conformer.ts +0 -647
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/transcode.ts +0 -67
- package/src/backends/mlx/assistant-target.ts +0 -46
- package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
- package/src/backends/mlx/autoregressive.ts +0 -108
- package/src/backends/mlx/batch-group.ts +0 -1790
- package/src/backends/mlx/cache-attachment.ts +0 -67
- package/src/backends/mlx/cache-layout.ts +0 -31
- package/src/backends/mlx/checkpoint-state.ts +0 -53
- package/src/backends/mlx/continuation-persistence.ts +0 -106
- package/src/backends/mlx/continuation-request.ts +0 -43
- package/src/backends/mlx/continuation.ts +0 -77
- package/src/backends/mlx/diffusion.ts +0 -39
- package/src/backends/mlx/draft-target.ts +0 -32
- package/src/backends/mlx/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -45
- package/src/backends/mlx/full-kv-row-append.ts +0 -32
- package/src/backends/mlx/full-kv-row-donor.ts +0 -54
- package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
- package/src/backends/mlx/gateway-binding.ts +0 -130
- package/src/backends/mlx/graph.ts +0 -38
- package/src/backends/mlx/kv-codec.ts +0 -9
- package/src/backends/mlx/kv-maintenance.ts +0 -133
- package/src/backends/mlx/kv-tensor-rows.ts +0 -212
- package/src/backends/mlx/memory-client.ts +0 -189
- package/src/backends/mlx/methods.ts +0 -90
- package/src/backends/mlx/model-prompt.ts +0 -164
- package/src/backends/mlx/model-serving.ts +0 -79
- package/src/backends/mlx/prefill-cohort.ts +0 -89
- package/src/backends/mlx/prefill-rows.ts +0 -200
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -41
- package/src/backends/mlx/rollback.ts +0 -89
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -242
- package/src/backends/mlx/speculative-group.ts +0 -359
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -114
- package/src/backends/mlx/state-rows.ts +0 -104
- package/src/backends/mlx/state-views.ts +0 -46
- package/src/backends/mlx/target-layout-capability.ts +0 -35
- package/src/backends/mlx/text-engine.ts +0 -73
- package/src/backends/mlx/transitioning-kv-rows.ts +0 -165
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3168
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -60
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -259
- package/src/contracts/prefix-cache.ts +0 -23
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -45
- package/src/contracts/tsconfig.json +0 -14
- package/src/dataset/generators.ts +0 -1087
- package/src/dataset/index.ts +0 -12
- package/src/dataset/job.ts +0 -81
- package/src/dataset/llm.ts +0 -71
- package/src/dataset/registry.ts +0 -528
- package/src/diffusion/diffusion-generate.ts +0 -507
- package/src/download.ts +0 -505
- package/src/embed.ts +0 -77
- package/src/engine/admission.ts +0 -59
- package/src/engine/cancellation.ts +0 -38
- package/src/engine/engine.ts +0 -56
- package/src/engine/execution-coordinator.ts +0 -70
- package/src/engine/execution-plan.ts +0 -53
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -72
- package/src/engine/session.ts +0 -247
- package/src/eval/README.md +0 -130
- package/src/eval/ifeval.ts +0 -81
- package/src/eval/kl-prompts.ts +0 -29
- package/src/eval/kl.ts +0 -590
- package/src/eval/perplexity.ts +0 -164
- package/src/eval/quality-db.ts +0 -92
- package/src/eval/reference-logits.ts +0 -207
- package/src/eval/runner.ts +0 -124
- package/src/eval/score.ts +0 -49
- package/src/eval/tasks/bfcl.ts +0 -535
- package/src/eval/tasks/gsm8k.ts +0 -98
- package/src/eval/tasks/hashhop.ts +0 -139
- package/src/eval/tasks/humaneval.ts +0 -275
- package/src/eval/tasks/ifeval.ts +0 -525
- package/src/eval/tasks/mmlu.ts +0 -157
- package/src/eval/ultrafeedback.ts +0 -60
- package/src/evaldb.ts +0 -199
- package/src/expert-io.ts +0 -393
- package/src/expert-offload-build.ts +0 -77
- package/src/expert-offload.ts +0 -76
- package/src/expert-residency.ts +0 -1134
- package/src/expert-usage.ts +0 -415
- package/src/fill/echo-index.ts +0 -188
- package/src/fill/fill-session.ts +0 -452
- package/src/fill/proposal.ts +0 -68
- package/src/fill/schema-rows.ts +0 -506
- package/src/fill/tool-boundary.ts +0 -103
- package/src/fit.ts +0 -249
- package/src/generate.ts +0 -1346
- package/src/grammar.ts +0 -591
- package/src/harness-pi.ts +0 -184
- package/src/hf-push.ts +0 -628
- package/src/hub-rest.ts +0 -242
- package/src/index.ts +0 -63
- package/src/inference/denoising.ts +0 -17
- package/src/inference/draft-acceptance.ts +0 -41
- package/src/inference/draft-output.ts +0 -49
- package/src/inference/graph.ts +0 -33
- package/src/inference/prefill.ts +0 -39
- package/src/inference/rollback.ts +0 -7
- package/src/jobs/index.ts +0 -31
- package/src/jobs/job-entry.ts +0 -107
- package/src/jobs/task-client.ts +0 -19
- package/src/jobs/types.ts +0 -2
- package/src/kv-scheme.ts +0 -257
- package/src/kv-store.ts +0 -1099
- package/src/lab/README.md +0 -18
- package/src/lab/curve/curve-designer.html +0 -303
- package/src/lab/curve/curve-sampler.ts +0 -122
- package/src/lab/expert-trace/expert-trace.ts +0 -69
- package/src/lab/paged-kv/paged-kv-rows.ts +0 -83
- package/src/lab/paged-kv/paged-kv.ts +0 -270
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -49
- package/src/media-fetch.ts +0 -442
- package/src/memory/article.ts +0 -558
- package/src/memory/chunk-validation.ts +0 -150
- package/src/memory/chunk.ts +0 -318
- package/src/memory/cluster.ts +0 -351
- package/src/memory/coverage.ts +0 -134
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +0 -253
- package/src/memory/entity.ts +0 -203
- package/src/memory/gate.ts +0 -187
- package/src/memory/history.ts +0 -152
- package/src/memory/ingest.ts +0 -335
- package/src/memory/model.ts +0 -183
- package/src/memory/normalize.ts +0 -671
- package/src/memory/parse.ts +0 -22
- package/src/memory/pipeline.ts +0 -275
- package/src/memory/prompts.ts +0 -65
- package/src/memory/query.ts +0 -334
- package/src/memory/reconcile.ts +0 -734
- package/src/memory/redirect.ts +0 -108
- package/src/memory/reindex.ts +0 -325
- package/src/memory/resolve.ts +0 -307
- package/src/memory/rest.ts +0 -245
- package/src/memory/route.ts +0 -272
- package/src/memory/schedule.ts +0 -165
- package/src/memory/stages.ts +0 -777
- package/src/memory/synthesize.ts +0 -1638
- package/src/memory/tools.ts +0 -749
- package/src/memory/vault.ts +0 -1194
- package/src/memory/wikify.ts +0 -1064
- package/src/mlx/array.ts +0 -388
- package/src/mlx/autograd.ts +0 -304
- package/src/mlx/checkpoint.ts +0 -149
- package/src/mlx/compile.ts +0 -168
- package/src/mlx/custom-vjp.ts +0 -188
- package/src/mlx/ffi.ts +0 -512
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/ops.ts +0 -1021
- package/src/mlx/quantized-rows.ts +0 -36
- package/src/mlx/token-bitmask.ts +0 -52
- package/src/mlx/turboquant-kv-decode.ts +0 -82
- package/src/mlx/turboquant-kv-inverse.ts +0 -122
- package/src/mlx/turboquant-ops.ts +0 -861
- package/src/mlx/turboquant-tables.ts +0 -237
- package/src/mmap.ts +0 -90
- package/src/model/artifact-identity.ts +0 -73
- package/src/model/batched-kv.ts +0 -235
- package/src/model/batched-mask.ts +0 -279
- package/src/model/batched-quant.ts +0 -212
- package/src/model/batched-quantized-kv.ts +0 -144
- package/src/model/batched-rotating-quant.ts +0 -353
- package/src/model/batched-rotating-state.ts +0 -149
- package/src/model/batched-rotating.ts +0 -338
- package/src/model/batched-row-storage.ts +0 -179
- package/src/model/batched-ssm.ts +0 -52
- package/src/model/batched-turboquant-kv.ts +0 -93
- package/src/model/compiled-decode.ts +0 -712
- package/src/model/decoded-kv-donor.ts +0 -12
- package/src/model/delayed-quantized-kv.ts +0 -72
- package/src/model/delayed-rotating-quantized-kv.ts +0 -93
- package/src/model/delayed-turboquant-kv.ts +0 -55
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -189
- package/src/model/fingerprint.ts +0 -33
- package/src/model/flash-attention.ts +0 -694
- package/src/model/full-prefill-padding.ts +0 -56
- package/src/model/full-prefill-row.ts +0 -105
- package/src/model/gemma4-base.ts +0 -2351
- package/src/model/gemma4.ts +0 -1315
- package/src/model/generated/gemma4-12b.ts +0 -429
- package/src/model/generated/gemma4-26b.ts +0 -448
- package/src/model/generated/gemma4-e4b.ts +0 -717
- package/src/model/generated/index.ts +0 -18
- package/src/model/glm52-atlas.ts +0 -511
- package/src/model/glm52-cache.ts +0 -792
- package/src/model/glm52-config.ts +0 -262
- package/src/model/glm52-container.ts +0 -305
- package/src/model/glm52-coupling.ts +0 -388
- package/src/model/glm52-dsa.ts +0 -606
- package/src/model/glm52-expert-layout.ts +0 -253
- package/src/model/glm52-layout.ts +0 -152
- package/src/model/glm52-memory.ts +0 -500
- package/src/model/glm52-mla.ts +0 -792
- package/src/model/glm52-moe.ts +0 -223
- package/src/model/glm52-pilot.ts +0 -537
- package/src/model/glm52-reference.ts +0 -445
- package/src/model/glm52-residency.ts +0 -557
- package/src/model/glm52-resident-weights.ts +0 -135
- package/src/model/glm52-streamed-experts.ts +0 -453
- package/src/model/glm52-streamed-metal.ts +0 -610
- package/src/model/glm52-weights.ts +0 -503
- package/src/model/glm52.ts +0 -1132
- package/src/model/implementation.ts +0 -69
- package/src/model/kv-attention-view.ts +0 -67
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -443
- package/src/model/qwen-append.ts +0 -12
- package/src/model/qwen-conv.ts +0 -52
- package/src/model/qwen3-delta-state.ts +0 -74
- package/src/model/qwen3-delta.ts +0 -451
- package/src/model/qwen3-moe.ts +0 -404
- package/src/model/qwen3-mrope.ts +0 -252
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -819
- package/src/model/rotating-kv-donor.ts +0 -35
- package/src/model/rotating-kv-layout.ts +0 -262
- package/src/model/rotating-row-transaction.ts +0 -35
- package/src/model/speculative-rotating-kv.ts +0 -101
- package/src/model/ssm-prefill-padding.ts +0 -39
- package/src/model/support.ts +0 -102
- package/src/model/trellis-balanced-scatter.ts +0 -51
- package/src/model/trellis-linear.ts +0 -672
- package/src/model/trellis-shared-m.ts +0 -151
- package/src/model/trellis-shared-scatter.ts +0 -144
- package/src/model/trellis-splitk-prefill.ts +0 -158
- package/src/model/trellis-tiled-prefill.ts +0 -136
- package/src/model/trellis-vector-expand.ts +0 -62
- package/src/model/trellis-wide-prefill.ts +0 -94
- package/src/model/turboquant-codec.ts +0 -83
- package/src/model/universal/archs.ts +0 -332
- package/src/model/universal/dense.ts +0 -612
- package/src/model/universal/modules.ts +0 -170
- package/src/model/universal/rope.ts +0 -381
- package/src/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native-pack.ts +0 -162
- package/src/pi-provider.ts +0 -53
- package/src/pi-session.ts +0 -81
- package/src/pi-terminal.ts +0 -274
- package/src/pi-web.ts +0 -1932
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -460
- package/src/quantize/allocator.ts +0 -627
- package/src/quantize/atomic-output.ts +0 -38
- package/src/quantize/calibration.ts +0 -181
- package/src/quantize/config-writer.ts +0 -145
- package/src/quantize/index.ts +0 -84
- package/src/quantize/job.ts +0 -162
- package/src/quantize/quantizer.ts +0 -666
- package/src/quantize/rotate.ts +0 -603
- package/src/quantize/safetensors-writer.ts +0 -249
- package/src/quantize/sensitivity.ts +0 -216
- package/src/quantize/trellis.ts +0 -586
- package/src/quantize/weight-transform.ts +0 -292
- package/src/registry.ts +0 -602
- package/src/runtime-config.ts +0 -81
- package/src/safetensors.ts +0 -158
- package/src/sampler.ts +0 -1011
- package/src/serve/admin-routes.ts +0 -184
- package/src/serve/assets/curve-terrain.html +0 -105
- package/src/serve/assets/training-inference-map.html +0 -575
- package/src/serve/aux-routes.ts +0 -132
- package/src/serve/batch-scheduler.ts +0 -8
- package/src/serve/checkpoint-identity.ts +0 -64
- package/src/serve/generation-gateway.ts +0 -496
- package/src/serve/isolate.ts +0 -609
- package/src/serve/kv-budget.ts +0 -14
- package/src/serve/lab-routes.ts +0 -341
- package/src/serve/lane-registry.ts +0 -45
- package/src/serve/model-admin-routes.ts +0 -155
- package/src/serve/model-binding.ts +0 -43
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -147
- package/src/serve/response-lease.ts +0 -24
- package/src/serve/session-search.ts +0 -326
- package/src/serve/static-routes.ts +0 -101
- package/src/server.ts +0 -1314
- package/src/spec/assistant-rows.ts +0 -92
- package/src/spec/assistant-source.ts +0 -126
- package/src/spec/cache-identity.ts +0 -22
- package/src/spec/deepspec-rows.ts +0 -29
- package/src/spec/deepspec-source.ts +0 -145
- package/src/spec/dflash-source.ts +0 -149
- package/src/spec/drafter.ts +0 -304
- package/src/spec/dspark/ab-stats.ts +0 -136
- package/src/spec/dspark/calibration.ts +0 -112
- package/src/spec/dspark/data-dflash.ts +0 -156
- package/src/spec/dspark/data.ts +0 -201
- package/src/spec/dspark/deepspec-module.ts +0 -776
- package/src/spec/dspark/generate-dflash.ts +0 -245
- package/src/spec/dspark/generate.ts +0 -290
- package/src/spec/dspark/loader.ts +0 -23
- package/src/spec/dspark/loss.ts +0 -213
- package/src/spec/dspark/module-dflash.ts +0 -560
- package/src/spec/dspark/module.ts +0 -540
- package/src/spec/dspark/quantize-drafter.ts +0 -91
- package/src/spec/dspark/sample.ts +0 -112
- package/src/spec/generate.ts +0 -276
- package/src/spec/glm52-mtp-graph.ts +0 -29
- package/src/spec/glm52-mtp-rows.ts +0 -132
- package/src/spec/glm52-mtp-source.ts +0 -317
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -204
- package/src/spec/projected-draft-rows.ts +0 -73
- package/src/spec/qwen-mtp-module.ts +0 -171
- package/src/spec/qwen-mtp-rows.ts +0 -219
- package/src/spec/qwen-mtp-source.ts +0 -383
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -585
- package/src/spec/source.ts +0 -215
- package/src/spec/two-model-rows.ts +0 -61
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -372
- package/src/ssd-durability.ts +0 -211
- package/src/tokenizer.ts +0 -335
- package/src/tool-approvals.ts +0 -131
- package/src/tool-call.ts +0 -695
- package/src/train/dataset.ts +0 -484
- package/src/train/diffusion-lora.ts +0 -216
- package/src/train/export.ts +0 -32
- package/src/train/flash-cce.ts +0 -1234
- package/src/train/forward.ts +0 -266
- package/src/train/fuse.ts +0 -243
- package/src/train/index.ts +0 -46
- package/src/train/job.ts +0 -205
- package/src/train/lora-params.ts +0 -254
- package/src/train/loss.ts +0 -1591
- package/src/train/merge.ts +0 -195
- package/src/train/optimizer.ts +0 -183
- package/src/train/prefix-shared.ts +0 -490
- package/src/train/rank.ts +0 -137
- package/src/train/segmented.ts +0 -1828
- package/src/train/steel-qmm-header.ts +0 -1461
- package/src/train/trainer.ts +0 -1221
- package/src/train/watch.ts +0 -542
- package/src/vision/diffusion-vision.ts +0 -620
- package/src/vision/embedder.ts +0 -168
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -373
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -196
- package/src/vision/qwen3vl-tower.ts +0 -555
- package/src/vision/siglip.ts +0 -599
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2143
- package/src/web/app.js +0 -5703
- package/src/web/skills/memory/SKILL.md +0 -120
- package/src/web/skills/web-research/SKILL.md +0 -46
- package/src/web/skills.ts +0 -97
- package/src/web/src/adapters-panel.ts +0 -288
- package/src/web/src/assistant.ts +0 -451
- package/src/web/src/chat.ts +0 -1057
- package/src/web/src/composer.ts +0 -959
- package/src/web/src/dataset.ts +0 -132
- package/src/web/src/finetune.ts +0 -267
- package/src/web/src/hub.ts +0 -428
- package/src/web/src/main.ts +0 -98
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +0 -572
- package/src/web/src/model-picker.ts +0 -159
- package/src/web/src/palette.ts +0 -346
- package/src/web/src/protocol.ts +0 -26
- package/src/web/src/quantize.ts +0 -167
- package/src/web/src/rag.ts +0 -274
- package/src/web/src/sessions.ts +0 -469
- package/src/web/src/shell.ts +0 -804
- package/src/web/src/ui-catalog.ts +0 -78
- package/src/web/vendor/README.md +0 -61
- package/src/web-assets.ts +0 -47
- package/src/web-tools.ts +0 -478
- package/src/weights.ts +0 -255
- /package/src/{serve → server}/completion-sink.ts +0 -0
- /package/src/web/{icon.svg → public/icon.svg} +0 -0
- /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
- /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// GENERATED-ADJACENT source module — part of the src/web/src/* split (plan
|
|
2
|
-
// §7/§9 Phase 3, §6.6 "app-aware assistant"). Built into src/web/app.js by
|
|
3
|
-
// scripts/build-web.ts.
|
|
4
|
-
//
|
|
5
|
-
// Shared route IDs keep browser navigation and server validation in agreement.
|
|
6
|
-
// Spotlight selectors and overlay IDs belong to the browser.
|
|
7
|
-
|
|
8
|
-
/** A hash-routed page (shell.ts's Route union, minus "routes" — the DAG
|
|
9
|
-
* diagram tab isn't a useful assistant destination and is itself
|
|
10
|
-
* feature-detected/hidden when unavailable). */
|
|
11
|
-
import { APP_ROUTE_IDS as ROUTE_IDS } from "../../contracts/pi";
|
|
12
|
-
export { ROUTE_IDS };
|
|
13
|
-
export type RouteId = (typeof ROUTE_IDS)[number];
|
|
14
|
-
|
|
15
|
-
/** A non-route overlay/panel the assistant can navigate to or spotlight
|
|
16
|
-
* within. Opening one of these does not change location.hash. */
|
|
17
|
-
export const VIEW_IDS = ["memory-panel", "hub-panel", "settings", "adapters-panel"] as const;
|
|
18
|
-
export type ViewId = (typeof VIEW_IDS)[number];
|
|
19
|
-
|
|
20
|
-
export function isRouteId(v: string): v is RouteId {
|
|
21
|
-
return (ROUTE_IDS as readonly string[]).includes(v);
|
|
22
|
-
}
|
|
23
|
-
export function isViewId(v: string): v is ViewId {
|
|
24
|
-
return (VIEW_IDS as readonly string[]).includes(v);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Human labels for routes, used in the "unknown route" tool-error message
|
|
28
|
-
* and nowhere else — kept tiny on purpose. */
|
|
29
|
-
export const ROUTE_LABELS: Record<RouteId, string> = {
|
|
30
|
-
chat: "Chat",
|
|
31
|
-
quantize: "Quantize",
|
|
32
|
-
finetune: "Fine-tune",
|
|
33
|
-
dataset: "Build Dataset",
|
|
34
|
-
status: "Status",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/** A curated spotlight target: a stable id -> the selector to highlight +
|
|
38
|
-
* a human label for the popover title, plus which route/view it lives on
|
|
39
|
-
* (spotlight_ui navigates there first if the browser isn't already on it).
|
|
40
|
-
* This is the "catalog target" resolution path in resolve-spotlight.ts's
|
|
41
|
-
* terms — the fallback when ref/label/selector don't resolve from the live
|
|
42
|
-
* DOM or the last uiSnapshot. Anchors added to app.html via data-spotlight
|
|
43
|
-
* in this same wave; ids are stable and boring (element id minus "chat-"
|
|
44
|
-
* prefix noise where it reads fine either way). */
|
|
45
|
-
export interface SpotlightTarget {
|
|
46
|
-
selector: string;
|
|
47
|
-
label: string;
|
|
48
|
-
route?: RouteId;
|
|
49
|
-
view?: ViewId;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const SPOTLIGHT_TARGETS: Record<string, SpotlightTarget> = {
|
|
53
|
-
composer: { selector: "#chat-box", label: "Message box", route: "chat" },
|
|
54
|
-
send: { selector: "#chat-send", label: "Send", route: "chat" },
|
|
55
|
-
"new-chat": { selector: "#chat-new", label: "New chat", route: "chat" },
|
|
56
|
-
"session-search": { selector: "#chat-sess-search", label: "Search chats", route: "chat" },
|
|
57
|
-
"sampling-pill": { selector: "#chat-sampling", label: "Sampling", route: "chat" },
|
|
58
|
-
"prompt-pill": { selector: "#chat-sysprompt", label: "System prompt", route: "chat" },
|
|
59
|
-
"adapter-select": { selector: "#chat-adapter", label: "LoRA adapter", route: "chat" },
|
|
60
|
-
"memory-entry": { selector: "#chat-memory-entry", label: "Memory", route: "chat" },
|
|
61
|
-
"developer-toggle": { selector: "#nav-developer", label: "Developer toggle" },
|
|
62
|
-
"model-picker": { selector: "#nav-model", label: "Active model" },
|
|
63
|
-
"hub-browse": { selector: "#model-pop-browse", label: "Browse models", view: "hub-panel" },
|
|
64
|
-
"quantize-source": { selector: "#q-model", label: "Source model", route: "quantize" },
|
|
65
|
-
"finetune-base": { selector: "#f-model", label: "Base model", route: "finetune" },
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export function getSpotlightTarget(id: string): SpotlightTarget | null {
|
|
69
|
-
return SPOTLIGHT_TARGETS[id] ?? null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Resolve a free-form route/page string (either a bare route id or a
|
|
73
|
-
* "#/route" hash) the way navigate_app's tool param is described to the
|
|
74
|
-
* model. Returns null for anything not in ROUTE_IDS. */
|
|
75
|
-
export function resolveRouteId(routeOrPage: string): RouteId | null {
|
|
76
|
-
const bare = routeOrPage.trim().replace(/^#\/?/, "");
|
|
77
|
-
return isRouteId(bare) ? bare : null;
|
|
78
|
-
}
|
package/src/web/vendor/README.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# src/web/vendor/ — vendored, no-CDN web assets
|
|
2
|
-
|
|
3
|
-
Assets here are bundled locally and served same-origin by `src/server.ts`
|
|
4
|
-
(`GET /assets/<name>`, `with { type: "text" }` import — same pattern as
|
|
5
|
-
`src/web/app.html` itself). Nothing in this app loads a script or
|
|
6
|
-
stylesheet from a CDN; this directory is where that tradeoff lives.
|
|
7
|
-
|
|
8
|
-
## hljs.js — syntax highlighting
|
|
9
|
-
|
|
10
|
-
`hljs.js` is [highlight.js](https://github.com/highlightjs/highlight.js)
|
|
11
|
-
(BSD-3-Clause, license text in `hljs-LICENSE`) core engine + a curated
|
|
12
|
-
language set: javascript/typescript, python, bash/shell, json, html/xml,
|
|
13
|
-
css, c/cpp, rust, go, sql, yaml, markdown, diff, plaintext. Registered
|
|
14
|
-
languages expose themselves as `class="language-<name>"` on `<code>`
|
|
15
|
-
elements — see `mdCodeBlock()` and `highlightIn()` in `app.html`.
|
|
16
|
-
|
|
17
|
-
To rebuild (e.g. to bump the version or change the language set):
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
mkdir /tmp/hljs-build && cd /tmp/hljs-build
|
|
21
|
-
npm init -y
|
|
22
|
-
npm install highlight.js@<version> --no-save
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Write an `entry.js` that imports `highlight.js/lib/core` and
|
|
26
|
-
`highlight.js/lib/languages/<lang>` for each language you want, calling
|
|
27
|
-
`hljs.registerLanguage(name, lang)` for each (see mlx-bun's own build log /
|
|
28
|
-
commit that introduced this file for the exact entry.js used), then:
|
|
29
|
-
|
|
30
|
-
```sh
|
|
31
|
-
globalThis.hljs = hljs; # inside entry.js, after registering
|
|
32
|
-
bun build ./entry.js --minify --outfile=hljs.bundle.js
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Prepend a short license/provenance header comment (BSD-3-Clause,
|
|
36
|
-
upstream URL, rebuild instructions — copy the one already at the top of
|
|
37
|
-
`hljs.js`) and copy the result over `src/web/vendor/hljs.js`.
|
|
38
|
-
|
|
39
|
-
**Size discipline:** the hygiene gate (`scripts/check-hygiene.ts`) fails
|
|
40
|
-
any tracked file over 1 MB unless it's on the explicit allowlist. The
|
|
41
|
-
current bundle (core + 14 languages) is ~83 KB — comfortably under the
|
|
42
|
-
cap. If you add enough languages to bust 1 MB, either trim the language
|
|
43
|
-
list or add a justified, size-capped `ALLOW` entry in
|
|
44
|
-
`scripts/check-hygiene.ts` (see the existing entries there for the
|
|
45
|
-
pattern) — don't silently let it grow past the cap.
|
|
46
|
-
|
|
47
|
-
`hljs-theme.css` is NOT generated from highlight.js's stock themes — it's
|
|
48
|
-
hand-written, mapping the standard `hljs-*` token classes onto mlx-bun's
|
|
49
|
-
own design tokens (see the file's own header comment) so code blocks match
|
|
50
|
-
the app's palette in both light and dark.
|
|
51
|
-
|
|
52
|
-
## KaTeX (math) — deliberately not vendored
|
|
53
|
-
|
|
54
|
-
Investigated for the same treatment; skipped. KaTeX's CSS depends on its
|
|
55
|
-
own bundled math font (`dist/fonts/`, ~60 files / ~1.1 MB of woff2/woff/ttf)
|
|
56
|
-
for correct glyph metrics — there's no clean "system font fallback" path
|
|
57
|
-
that renders math correctly. Vendoring it would mean adding ~1 MB of
|
|
58
|
-
binary font files, which the hygiene gate is specifically designed to keep
|
|
59
|
-
out of this repo (see `scripts/check-hygiene.ts`'s header). Needs the
|
|
60
|
-
build-step/asset-hosting decision from `docs/design/web-chat-redesign.md`
|
|
61
|
-
§7 before it's worth revisiting.
|
package/src/web-assets.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Embedded web app — the unified SPA (chat / quantize / finetune /
|
|
2
|
-
// dataset / status). `with { type: "text" }` inlines the file in both
|
|
3
|
-
// `bun run` and the compiled single binary. bun-types types *.html
|
|
4
|
-
// imports as HTMLBundle (the html loader), but the text attribute makes
|
|
5
|
-
// the runtime value a string — hence the double cast.
|
|
6
|
-
import appHtml from "./web/app.html" with { type: "text" };
|
|
7
|
-
import curveDesignerHtml from "./lab/curve/curve-designer.html" with { type: "text" };
|
|
8
|
-
// Vendored, no-CDN static assets referenced by app.html (convention: any
|
|
9
|
-
// self-contained JS/CSS too big to inline gets `with { type: "text" }`
|
|
10
|
-
// imported here and served under /assets/<name>; see src/web/vendor/README
|
|
11
|
-
// for how hljs.js was built). Add new vendored assets the same way.
|
|
12
|
-
import hljsJs from "./web/vendor/hljs.js" with { type: "text" };
|
|
13
|
-
import hljsCss from "./web/vendor/hljs-theme.css" with { type: "text" };
|
|
14
|
-
// The frontend bundle (plan §7/§9 Phase 2 module split): GENERATED from
|
|
15
|
-
// src/web/src/*.ts by `bun scripts/build-web.ts` — see that file's header
|
|
16
|
-
// and tests/using/web-build.test.ts (the freshness gate). Same
|
|
17
|
-
// with { type: "text" } + /assets/<name> pattern as the vendored assets
|
|
18
|
-
// above; app.html's <script defer src="/assets/app.js"> loads it.
|
|
19
|
-
import appJs from "./web/app.js" with { type: "text" };
|
|
20
|
-
// PWA installability (plan §9 Phase 3, beat-matrix Axis 10): a manifest +
|
|
21
|
-
// a single inline SVG icon (no binary PNGs — the hygiene gate forbids
|
|
22
|
-
// tracked binary files; some browsers won't show an SVG app icon, which is
|
|
23
|
-
// an accepted tradeoff, see docs/reference/server-config.md) + a shell-
|
|
24
|
-
// only service worker. Same with { type: "text" } + /assets/<name>-shaped
|
|
25
|
-
// pattern as the vendored assets above; see src/web/sw.js's header for why
|
|
26
|
-
// it deliberately does NOT cache API/WS traffic.
|
|
27
|
-
import manifestWebmanifest from "./web/manifest.webmanifest" with { type: "text" };
|
|
28
|
-
import iconSvg from "./web/icon.svg" with { type: "text" };
|
|
29
|
-
import swJs from "./web/sw.js" with { type: "text" };
|
|
30
|
-
const APP_PAGE = appHtml as unknown as string;
|
|
31
|
-
const HLJS_JS = hljsJs as unknown as string;
|
|
32
|
-
const HLJS_CSS = hljsCss as unknown as string;
|
|
33
|
-
const APP_JS = appJs as unknown as string;
|
|
34
|
-
const MANIFEST_WEBMANIFEST = manifestWebmanifest as unknown as string;
|
|
35
|
-
const ICON_SVG = iconSvg as unknown as string;
|
|
36
|
-
const SW_JS = swJs as unknown as string;
|
|
37
|
-
const CURVE_PAGE = curveDesignerHtml as unknown as string;
|
|
38
|
-
export const STATIC_ROUTE_ASSETS = {
|
|
39
|
-
appPage: APP_PAGE,
|
|
40
|
-
appJs: APP_JS,
|
|
41
|
-
hljsJs: HLJS_JS,
|
|
42
|
-
hljsCss: HLJS_CSS,
|
|
43
|
-
manifest: MANIFEST_WEBMANIFEST,
|
|
44
|
-
iconSvg: ICON_SVG,
|
|
45
|
-
serviceWorker: SW_JS,
|
|
46
|
-
curvePage: CURVE_PAGE,
|
|
47
|
-
};
|
package/src/web-tools.ts
DELETED
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
// mlx-bun web-tools — outward-facing capabilities for the web chat agent:
|
|
2
|
-
// web_search, web_fetch, and weather. These are pi custom tools (defineTool)
|
|
3
|
-
// registered alongside the built-in coding tools so the local model can pull
|
|
4
|
-
// in current, real-world information instead of only inspecting the machine.
|
|
5
|
-
//
|
|
6
|
-
// Design notes:
|
|
7
|
-
// - Keyless by default. weather uses Open-Meteo (no key, no anti-bot),
|
|
8
|
-
// web_fetch is a plain HTTP GET, and web_search defaults to DuckDuckGo's
|
|
9
|
-
// keyless HTML endpoint. Setting TAVILY_API_KEY or BRAVE_API_KEY upgrades
|
|
10
|
-
// web_search to that provider's JSON API (more reliable, ranked results).
|
|
11
|
-
// - Non-mutating: none of these touch the user's filesystem, so the web
|
|
12
|
-
// chat's approval gate auto-allows them (they're not in GATED_TOOLS).
|
|
13
|
-
// They DO make outbound network requests — that's the one thing that
|
|
14
|
-
// leaves the machine, and only when the user asks for live info.
|
|
15
|
-
// - execute() never throws on a network/upstream failure: it returns the
|
|
16
|
-
// error as tool text so the model can read it and adapt (retry, tell the
|
|
17
|
-
// user, fall back) rather than the turn aborting.
|
|
18
|
-
//
|
|
19
|
-
// The pure helpers (htmlToText, wmoCodeToText, parseDuckDuckGoHtml, the
|
|
20
|
-
// formatters) are exported and unit-tested without touching the network.
|
|
21
|
-
|
|
22
|
-
import { defineTool, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
23
|
-
import { Type } from "typebox";
|
|
24
|
-
import {
|
|
25
|
-
fetchRestrictedHttpBytes,
|
|
26
|
-
type MediaFetchPolicy,
|
|
27
|
-
type RestrictedFetchDependencies,
|
|
28
|
-
} from "./media-fetch";
|
|
29
|
-
|
|
30
|
-
/** Tool names, exported so pi-web can add them to its tool allowlist. */
|
|
31
|
-
export const WEB_TOOL_NAMES = ["web_search", "web_fetch", "weather"] as const;
|
|
32
|
-
|
|
33
|
-
/** Browser-ish UA: some endpoints (DuckDuckGo) serve a challenge to blanks. */
|
|
34
|
-
const USER_AGENT =
|
|
35
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " +
|
|
36
|
-
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36";
|
|
37
|
-
|
|
38
|
-
const FETCH_TIMEOUT_MS = 20_000;
|
|
39
|
-
const MAX_FETCH_BYTES = 1024 * 1024;
|
|
40
|
-
/** Cap on text returned to the model from a fetched page. */
|
|
41
|
-
const MAX_FETCH_CHARS = 12_000;
|
|
42
|
-
const DEFAULT_SEARCH_RESULTS = 5;
|
|
43
|
-
const MAX_SEARCH_RESULTS = 10;
|
|
44
|
-
|
|
45
|
-
// ---- low-level fetch helper ------------------------------------------
|
|
46
|
-
|
|
47
|
-
/** Combine the tool's abort signal with a wall-clock timeout. */
|
|
48
|
-
function withTimeout(signal: AbortSignal | undefined, ms: number): AbortSignal {
|
|
49
|
-
const timeout = AbortSignal.timeout(ms);
|
|
50
|
-
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface FetchResult {
|
|
54
|
-
ok: boolean;
|
|
55
|
-
status: number;
|
|
56
|
-
finalUrl: string;
|
|
57
|
-
contentType: string;
|
|
58
|
-
text: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface WebFetchOptions {
|
|
62
|
-
policy?: MediaFetchPolicy;
|
|
63
|
-
dependencies?: Partial<RestrictedFetchDependencies>;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const WEB_FETCH_POLICY: MediaFetchPolicy = {
|
|
67
|
-
allowPrivate: false,
|
|
68
|
-
privateDestinationHint:
|
|
69
|
-
"private/loopback destinations are never available to the model-driven web_fetch tool",
|
|
70
|
-
timeoutMs: FETCH_TIMEOUT_MS,
|
|
71
|
-
maxBytes: MAX_FETCH_BYTES,
|
|
72
|
-
maxRedirects: 5,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/** GET/POST a URL and read the body as text, with timeout + abort wiring. */
|
|
76
|
-
async function httpText(
|
|
77
|
-
url: string,
|
|
78
|
-
opts: { method?: string; headers?: Record<string, string>; body?: string; signal?: AbortSignal; timeoutMs?: number },
|
|
79
|
-
): Promise<FetchResult> {
|
|
80
|
-
const res = await fetch(url, {
|
|
81
|
-
method: opts.method ?? "GET",
|
|
82
|
-
headers: { "user-agent": USER_AGENT, ...opts.headers },
|
|
83
|
-
body: opts.body,
|
|
84
|
-
redirect: "follow",
|
|
85
|
-
signal: withTimeout(opts.signal, opts.timeoutMs ?? FETCH_TIMEOUT_MS),
|
|
86
|
-
});
|
|
87
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
88
|
-
const text = await res.text();
|
|
89
|
-
return { ok: res.ok, status: res.status, finalUrl: res.url || url, contentType, text };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Fetch a model-selected URL through the shared restricted-destination
|
|
94
|
-
* transport. Exported with dependency injection for deterministic,
|
|
95
|
-
* model-free security tests.
|
|
96
|
-
*/
|
|
97
|
-
export async function fetchWebText(
|
|
98
|
-
url: string,
|
|
99
|
-
signal?: AbortSignal,
|
|
100
|
-
options: WebFetchOptions = {},
|
|
101
|
-
): Promise<FetchResult> {
|
|
102
|
-
const result = await fetchRestrictedHttpBytes(
|
|
103
|
-
url,
|
|
104
|
-
"web",
|
|
105
|
-
options.policy ?? WEB_FETCH_POLICY,
|
|
106
|
-
{
|
|
107
|
-
signal,
|
|
108
|
-
dependencies: options.dependencies,
|
|
109
|
-
headers: {
|
|
110
|
-
"user-agent": USER_AGENT,
|
|
111
|
-
accept: "text/html,application/json,text/plain,*/*",
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
);
|
|
115
|
-
return {
|
|
116
|
-
ok: result.ok,
|
|
117
|
-
status: result.status,
|
|
118
|
-
finalUrl: result.finalUrl,
|
|
119
|
-
contentType: result.contentType,
|
|
120
|
-
text: new TextDecoder().decode(result.bytes),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Normalize anything thrown by fetch (timeout, DNS, abort) into a message. */
|
|
125
|
-
function describeFetchError(err: unknown): string {
|
|
126
|
-
if (err instanceof Error) {
|
|
127
|
-
if (err.name === "TimeoutError") return "request timed out";
|
|
128
|
-
if (err.name === "AbortError") return "request was cancelled";
|
|
129
|
-
return err.message;
|
|
130
|
-
}
|
|
131
|
-
return String(err);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ---- pure helpers (unit-tested) --------------------------------------
|
|
135
|
-
|
|
136
|
-
const NAMED_ENTITIES: Record<string, string> = {
|
|
137
|
-
amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " ", "#39": "'", "#34": '"',
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/** Decode the handful of HTML entities that show up in scraped text. */
|
|
141
|
-
export function decodeEntities(s: string): string {
|
|
142
|
-
return s.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, body: string) => {
|
|
143
|
-
if (body[0] === "#") {
|
|
144
|
-
const code = body[1] === "x" || body[1] === "X" ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10);
|
|
145
|
-
return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
|
|
146
|
-
}
|
|
147
|
-
return NAMED_ENTITIES[body] ?? whole;
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Strip all HTML tags from a fragment and decode entities (single-line). */
|
|
152
|
-
export function stripTags(html: string): string {
|
|
153
|
-
return decodeEntities(html.replace(/<[^>]*>/g, "")).replace(/\s+/g, " ").trim();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** Extract the <title> of an HTML document, if any. */
|
|
157
|
-
export function extractTitle(html: string): string | undefined {
|
|
158
|
-
const m = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
159
|
-
return m ? stripTags(m[1] ?? "") : undefined;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Reduce an HTML document to readable plain text: drop script/style/head,
|
|
164
|
-
* turn block-level boundaries into newlines, strip remaining tags, decode
|
|
165
|
-
* entities, and collapse runaway whitespace.
|
|
166
|
-
*/
|
|
167
|
-
export function htmlToText(html: string): string {
|
|
168
|
-
let s = html
|
|
169
|
-
.replace(/<!--[\s\S]*?-->/g, "")
|
|
170
|
-
.replace(/<(script|style|noscript|template|svg)[^>]*>[\s\S]*?<\/\1>/gi, "")
|
|
171
|
-
.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, "");
|
|
172
|
-
// Block-level close/break tags become newlines so structure survives.
|
|
173
|
-
s = s.replace(/<\/(p|div|section|article|li|ul|ol|tr|h[1-6]|header|footer|nav|blockquote)>/gi, "\n");
|
|
174
|
-
s = s.replace(/<br\s*\/?>/gi, "\n");
|
|
175
|
-
s = s.replace(/<[^>]+>/g, "");
|
|
176
|
-
s = decodeEntities(s);
|
|
177
|
-
// Collapse spaces/tabs, trim each line, squeeze blank-line runs.
|
|
178
|
-
s = s.replace(/[ \t\f\v]+/g, " ");
|
|
179
|
-
s = s.split("\n").map((line) => line.trim()).join("\n");
|
|
180
|
-
s = s.replace(/\n{3,}/g, "\n\n");
|
|
181
|
-
return s.trim();
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** Clamp text to a max length, appending a clear truncation marker. */
|
|
185
|
-
export function truncate(text: string, max: number): string {
|
|
186
|
-
if (text.length <= max) return text;
|
|
187
|
-
return text.slice(0, max) + `\n\n…[truncated ${text.length - max} more characters]`;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** WMO weather interpretation codes (Open-Meteo) → human description. */
|
|
191
|
-
export function wmoCodeToText(code: number): string {
|
|
192
|
-
const map: Record<number, string> = {
|
|
193
|
-
0: "clear sky", 1: "mainly clear", 2: "partly cloudy", 3: "overcast",
|
|
194
|
-
45: "fog", 48: "depositing rime fog",
|
|
195
|
-
51: "light drizzle", 53: "moderate drizzle", 55: "dense drizzle",
|
|
196
|
-
56: "light freezing drizzle", 57: "dense freezing drizzle",
|
|
197
|
-
61: "slight rain", 63: "moderate rain", 65: "heavy rain",
|
|
198
|
-
66: "light freezing rain", 67: "heavy freezing rain",
|
|
199
|
-
71: "slight snowfall", 73: "moderate snowfall", 75: "heavy snowfall",
|
|
200
|
-
77: "snow grains",
|
|
201
|
-
80: "slight rain showers", 81: "moderate rain showers", 82: "violent rain showers",
|
|
202
|
-
85: "slight snow showers", 86: "heavy snow showers",
|
|
203
|
-
95: "thunderstorm", 96: "thunderstorm with slight hail", 99: "thunderstorm with heavy hail",
|
|
204
|
-
};
|
|
205
|
-
return map[code] ?? `unknown (code ${code})`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export interface SearchResult {
|
|
209
|
-
title: string;
|
|
210
|
-
url: string;
|
|
211
|
-
snippet: string;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/** Resolve a DuckDuckGo redirect href (//duckduckgo.com/l/?uddg=…) to the real URL. */
|
|
215
|
-
export function resolveDdgHref(href: string): string {
|
|
216
|
-
const decoded = decodeEntities(href);
|
|
217
|
-
const m = decoded.match(/[?&]uddg=([^&]+)/);
|
|
218
|
-
if (m && m[1]) {
|
|
219
|
-
try {
|
|
220
|
-
return decodeURIComponent(m[1]);
|
|
221
|
-
} catch {
|
|
222
|
-
return decoded;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return decoded.startsWith("//") ? "https:" + decoded : decoded;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Parse the result list out of html.duckduckgo.com/html/ output. Pairs each
|
|
230
|
-
* result anchor (title + href) with the following snippet anchor by order.
|
|
231
|
-
* Returns [] when the page carries no results (e.g. a challenge/empty page).
|
|
232
|
-
*/
|
|
233
|
-
export function parseDuckDuckGoHtml(html: string, limit: number): SearchResult[] {
|
|
234
|
-
const results: SearchResult[] = [];
|
|
235
|
-
const linkRe = /<a[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
236
|
-
const snippetRe = /<a[^>]*class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
237
|
-
const snippets: string[] = [];
|
|
238
|
-
let sm: RegExpExecArray | null;
|
|
239
|
-
while ((sm = snippetRe.exec(html)) !== null) snippets.push(stripTags(sm[1] ?? ""));
|
|
240
|
-
let lm: RegExpExecArray | null;
|
|
241
|
-
let i = 0;
|
|
242
|
-
while ((lm = linkRe.exec(html)) !== null && results.length < limit) {
|
|
243
|
-
const url = resolveDdgHref(lm[1] ?? "");
|
|
244
|
-
const title = stripTags(lm[2] ?? "");
|
|
245
|
-
if (!title || !url) {
|
|
246
|
-
i++;
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
results.push({ title, url, snippet: snippets[i] ?? "" });
|
|
250
|
-
i++;
|
|
251
|
-
}
|
|
252
|
-
return results;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** Render a result list as numbered Markdown for the model. */
|
|
256
|
-
export function formatSearchResults(query: string, results: SearchResult[], provider: string): string {
|
|
257
|
-
if (results.length === 0) {
|
|
258
|
-
return `No results found for "${query}" (via ${provider}).`;
|
|
259
|
-
}
|
|
260
|
-
const lines = results.map((r, idx) => {
|
|
261
|
-
const head = `${idx + 1}. ${r.title}\n ${r.url}`;
|
|
262
|
-
return r.snippet ? `${head}\n ${r.snippet}` : head;
|
|
263
|
-
});
|
|
264
|
-
return `Search results for "${query}" (via ${provider}):\n\n${lines.join("\n\n")}`;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ---- weather: Open-Meteo geocode + forecast --------------------------
|
|
268
|
-
|
|
269
|
-
interface GeoPlace {
|
|
270
|
-
name: string;
|
|
271
|
-
latitude: number;
|
|
272
|
-
longitude: number;
|
|
273
|
-
country?: string;
|
|
274
|
-
admin1?: string;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/** Format an Open-Meteo forecast response into a compact human summary. */
|
|
278
|
-
export function formatWeather(place: GeoPlace, data: unknown, fahrenheit: boolean): string {
|
|
279
|
-
const d = data as {
|
|
280
|
-
current?: {
|
|
281
|
-
temperature_2m?: number;
|
|
282
|
-
apparent_temperature?: number;
|
|
283
|
-
relative_humidity_2m?: number;
|
|
284
|
-
wind_speed_10m?: number;
|
|
285
|
-
weather_code?: number;
|
|
286
|
-
};
|
|
287
|
-
daily?: { time?: string[]; weather_code?: number[]; temperature_2m_max?: number[]; temperature_2m_min?: number[]; precipitation_probability_max?: number[] };
|
|
288
|
-
};
|
|
289
|
-
const tUnit = fahrenheit ? "°F" : "°C";
|
|
290
|
-
const wUnit = fahrenheit ? "mph" : "km/h";
|
|
291
|
-
const where = [place.name, place.admin1, place.country].filter(Boolean).join(", ");
|
|
292
|
-
const lines: string[] = [`Weather for ${where}:`];
|
|
293
|
-
|
|
294
|
-
const cur = d.current;
|
|
295
|
-
if (cur?.temperature_2m != null) {
|
|
296
|
-
const desc = cur.weather_code != null ? `, ${wmoCodeToText(cur.weather_code)}` : "";
|
|
297
|
-
const parts = [`${Math.round(cur.temperature_2m)}${tUnit}${desc}`];
|
|
298
|
-
if (cur.apparent_temperature != null) parts.push(`feels like ${Math.round(cur.apparent_temperature)}${tUnit}`);
|
|
299
|
-
if (cur.relative_humidity_2m != null) parts.push(`humidity ${Math.round(cur.relative_humidity_2m)}%`);
|
|
300
|
-
if (cur.wind_speed_10m != null) parts.push(`wind ${Math.round(cur.wind_speed_10m)} ${wUnit}`);
|
|
301
|
-
lines.push(`Now: ${parts.join(", ")}.`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const daily = d.daily;
|
|
305
|
-
if (daily?.time?.length) {
|
|
306
|
-
lines.push("");
|
|
307
|
-
lines.push("Forecast:");
|
|
308
|
-
for (let i = 0; i < daily.time.length; i++) {
|
|
309
|
-
const code = daily.weather_code?.[i];
|
|
310
|
-
const hi = daily.temperature_2m_max?.[i];
|
|
311
|
-
const lo = daily.temperature_2m_min?.[i];
|
|
312
|
-
const pop = daily.precipitation_probability_max?.[i];
|
|
313
|
-
const desc = code != null ? wmoCodeToText(code) : "";
|
|
314
|
-
const range = hi != null && lo != null ? `${Math.round(lo)}–${Math.round(hi)}${tUnit}` : "";
|
|
315
|
-
const rain = pop != null ? `, precip ${pop}%` : "";
|
|
316
|
-
lines.push(`- ${daily.time[i]}: ${[range, desc].filter(Boolean).join(", ")}${rain}`);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return lines.join("\n");
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async function geocode(name: string, signal: AbortSignal | undefined): Promise<GeoPlace | undefined> {
|
|
323
|
-
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(name)}&count=1&language=en&format=json`;
|
|
324
|
-
const res = await httpText(url, { signal });
|
|
325
|
-
if (!res.ok) return undefined;
|
|
326
|
-
const json = JSON.parse(res.text) as { results?: GeoPlace[] };
|
|
327
|
-
return json.results?.[0];
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// ---- web_search providers --------------------------------------------
|
|
331
|
-
|
|
332
|
-
async function searchTavily(query: string, count: number, signal: AbortSignal | undefined, apiKey: string): Promise<SearchResult[]> {
|
|
333
|
-
const res = await httpText("https://api.tavily.com/search", {
|
|
334
|
-
method: "POST",
|
|
335
|
-
headers: { "content-type": "application/json" },
|
|
336
|
-
body: JSON.stringify({ api_key: apiKey, query, max_results: count, search_depth: "basic" }),
|
|
337
|
-
signal,
|
|
338
|
-
});
|
|
339
|
-
if (!res.ok) throw new Error(`Tavily returned HTTP ${res.status}`);
|
|
340
|
-
const json = JSON.parse(res.text) as { results?: Array<{ title?: string; url?: string; content?: string }> };
|
|
341
|
-
return (json.results ?? []).slice(0, count).map((r) => ({ title: r.title ?? r.url ?? "", url: r.url ?? "", snippet: r.content ?? "" }));
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
async function searchBrave(query: string, count: number, signal: AbortSignal | undefined, apiKey: string): Promise<SearchResult[]> {
|
|
345
|
-
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${count}`;
|
|
346
|
-
const res = await httpText(url, { headers: { "x-subscription-token": apiKey, accept: "application/json" }, signal });
|
|
347
|
-
if (!res.ok) throw new Error(`Brave returned HTTP ${res.status}`);
|
|
348
|
-
const json = JSON.parse(res.text) as { web?: { results?: Array<{ title?: string; url?: string; description?: string }> } };
|
|
349
|
-
return (json.web?.results ?? []).slice(0, count).map((r) => ({ title: r.title ?? r.url ?? "", url: r.url ?? "", snippet: stripTags(r.description ?? "") }));
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async function searchDuckDuckGo(query: string, count: number, signal: AbortSignal | undefined): Promise<SearchResult[]> {
|
|
353
|
-
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
354
|
-
const res = await httpText(url, { headers: { accept: "text/html" }, signal });
|
|
355
|
-
if (!res.ok) throw new Error(`DuckDuckGo returned HTTP ${res.status}`);
|
|
356
|
-
return parseDuckDuckGoHtml(res.text, count);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/** Pick the search backend from env; returns the provider label too. */
|
|
360
|
-
function resolveSearchProvider(): { name: string; run: (q: string, n: number, s: AbortSignal | undefined) => Promise<SearchResult[]> } {
|
|
361
|
-
const tavily = process.env.TAVILY_API_KEY;
|
|
362
|
-
if (tavily) return { name: "Tavily", run: (q, n, s) => searchTavily(q, n, s, tavily) };
|
|
363
|
-
const brave = process.env.BRAVE_API_KEY;
|
|
364
|
-
if (brave) return { name: "Brave", run: (q, n, s) => searchBrave(q, n, s, brave) };
|
|
365
|
-
return { name: "DuckDuckGo", run: searchDuckDuckGo };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// ---- tool definitions ------------------------------------------------
|
|
369
|
-
|
|
370
|
-
function textResult(text: string): { content: [{ type: "text"; text: string }]; details: Record<string, never> } {
|
|
371
|
-
return { content: [{ type: "text", text }], details: {} };
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const webSearchTool = defineTool({
|
|
375
|
-
name: "web_search",
|
|
376
|
-
label: "Web Search",
|
|
377
|
-
description:
|
|
378
|
-
"Search the web for current, real-world information (news, facts, docs, anything that changes over time or that you don't already know). " +
|
|
379
|
-
"Returns a ranked list of result titles, URLs, and snippets. Follow up with web_fetch to read a result in full.",
|
|
380
|
-
parameters: Type.Object({
|
|
381
|
-
query: Type.String({ description: "The search query." }),
|
|
382
|
-
count: Type.Optional(Type.Number({ description: `Number of results to return (default ${DEFAULT_SEARCH_RESULTS}, max ${MAX_SEARCH_RESULTS}).` })),
|
|
383
|
-
}),
|
|
384
|
-
execute: async (_id, params, signal) => {
|
|
385
|
-
const query = params.query.trim();
|
|
386
|
-
if (!query) return textResult("web_search needs a non-empty query.");
|
|
387
|
-
const count = Math.max(1, Math.min(MAX_SEARCH_RESULTS, Math.round(params.count ?? DEFAULT_SEARCH_RESULTS)));
|
|
388
|
-
const provider = resolveSearchProvider();
|
|
389
|
-
try {
|
|
390
|
-
const results = await provider.run(query, count, signal);
|
|
391
|
-
if (results.length === 0 && provider.name === "DuckDuckGo") {
|
|
392
|
-
return textResult(
|
|
393
|
-
`No results for "${query}". DuckDuckGo (the keyless default) may be rate-limiting; ` +
|
|
394
|
-
`set TAVILY_API_KEY or BRAVE_API_KEY for a reliable search backend.`,
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
return textResult(formatSearchResults(query, results, provider.name));
|
|
398
|
-
} catch (err) {
|
|
399
|
-
return textResult(`Web search failed (${provider.name}): ${describeFetchError(err)}`);
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
const webFetchTool = defineTool({
|
|
405
|
-
name: "web_fetch",
|
|
406
|
-
label: "Fetch URL",
|
|
407
|
-
description:
|
|
408
|
-
"Fetch a URL and return its contents as readable text (HTML pages are stripped to plain text). " +
|
|
409
|
-
"Use this to read a web page, article, or API/JSON endpoint — e.g. after web_search, or when the user gives you a link.",
|
|
410
|
-
parameters: Type.Object({
|
|
411
|
-
url: Type.String({ description: "The absolute http(s) URL to fetch." }),
|
|
412
|
-
}),
|
|
413
|
-
execute: async (_id, params, signal) => {
|
|
414
|
-
let url = params.url.trim();
|
|
415
|
-
if (!/^https?:\/\//i.test(url)) url = "https://" + url;
|
|
416
|
-
try {
|
|
417
|
-
const res = await fetchWebText(url, signal);
|
|
418
|
-
const header = `Fetched ${res.finalUrl} (HTTP ${res.status})`;
|
|
419
|
-
if (!res.ok) return textResult(`${header}\n\n${truncate(htmlToText(res.text), 2_000)}`);
|
|
420
|
-
const isHtml = /text\/html|application\/xhtml/i.test(res.contentType);
|
|
421
|
-
if (isHtml) {
|
|
422
|
-
const title = extractTitle(res.text);
|
|
423
|
-
const body = truncate(htmlToText(res.text), MAX_FETCH_CHARS);
|
|
424
|
-
return textResult(`${header}${title ? `\nTitle: ${title}` : ""}\n\n${body}`);
|
|
425
|
-
}
|
|
426
|
-
const isTextual = /text\/|application\/(json|xml|javascript|x-ndjson)|\+json|\+xml/i.test(res.contentType) || res.contentType === "";
|
|
427
|
-
if (!isTextual) {
|
|
428
|
-
return textResult(`${header}\n\nContent-Type ${res.contentType || "unknown"} is not text; not displaying ${res.text.length} bytes of binary data.`);
|
|
429
|
-
}
|
|
430
|
-
return textResult(`${header}\n\n${truncate(res.text, MAX_FETCH_CHARS)}`);
|
|
431
|
-
} catch (err) {
|
|
432
|
-
return textResult(`Failed to fetch ${url}: ${describeFetchError(err)}`);
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
const weatherTool = defineTool({
|
|
438
|
-
name: "weather",
|
|
439
|
-
label: "Weather",
|
|
440
|
-
description:
|
|
441
|
-
"Get current weather conditions and a short forecast for a place. Accepts a city/place name (e.g. 'Austin', 'Paris, France', 'Tokyo'). " +
|
|
442
|
-
"No API key required.",
|
|
443
|
-
parameters: Type.Object({
|
|
444
|
-
location: Type.String({ description: "City or place name to look up." }),
|
|
445
|
-
unit: Type.Optional(Type.String({ description: "Temperature unit: 'celsius' or 'fahrenheit' (default celsius)." })),
|
|
446
|
-
}),
|
|
447
|
-
execute: async (_id, params, signal) => {
|
|
448
|
-
const location = params.location.trim();
|
|
449
|
-
if (!location) return textResult("weather needs a location.");
|
|
450
|
-
const fahrenheit = params.unit === "fahrenheit";
|
|
451
|
-
try {
|
|
452
|
-
const place = await geocode(location, signal);
|
|
453
|
-
if (!place) return textResult(`Couldn't find a place named "${location}".`);
|
|
454
|
-
const params2 = new URLSearchParams({
|
|
455
|
-
latitude: String(place.latitude),
|
|
456
|
-
longitude: String(place.longitude),
|
|
457
|
-
current: "temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,wind_speed_10m",
|
|
458
|
-
daily: "weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max",
|
|
459
|
-
timezone: "auto",
|
|
460
|
-
forecast_days: "3",
|
|
461
|
-
});
|
|
462
|
-
if (fahrenheit) {
|
|
463
|
-
params2.set("temperature_unit", "fahrenheit");
|
|
464
|
-
params2.set("wind_speed_unit", "mph");
|
|
465
|
-
}
|
|
466
|
-
const res = await httpText(`https://api.open-meteo.com/v1/forecast?${params2.toString()}`, { signal });
|
|
467
|
-
if (!res.ok) return textResult(`Weather lookup failed: Open-Meteo returned HTTP ${res.status}.`);
|
|
468
|
-
return textResult(formatWeather(place, JSON.parse(res.text), fahrenheit));
|
|
469
|
-
} catch (err) {
|
|
470
|
-
return textResult(`Weather lookup failed: ${describeFetchError(err)}`);
|
|
471
|
-
}
|
|
472
|
-
},
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
/** All web tools, ready to pass to createAgentSession({ customTools }). */
|
|
476
|
-
export function createWebTools(): ToolDefinition[] {
|
|
477
|
-
return [webSearchTool, webFetchTool, weatherTool];
|
|
478
|
-
}
|