@nxisai/agent-harness 0.2.3 → 0.3.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.
Files changed (83) hide show
  1. package/README.md +94 -6
  2. package/dist/a2ui/stepper.d.ts +1 -1
  3. package/dist/a2ui/stepper.d.ts.map +1 -1
  4. package/dist/a2ui/stepper.js +2 -2
  5. package/dist/a2ui/stepper.js.map +1 -1
  6. package/dist/engine.d.ts +1 -0
  7. package/dist/engine.d.ts.map +1 -1
  8. package/dist/engine.js +1 -0
  9. package/dist/engine.js.map +1 -1
  10. package/dist/harness/harness.d.ts +38 -0
  11. package/dist/harness/harness.d.ts.map +1 -1
  12. package/dist/harness/harness.js +127 -4
  13. package/dist/harness/harness.js.map +1 -1
  14. package/dist/harness/renderer.d.ts +30 -0
  15. package/dist/harness/renderer.d.ts.map +1 -1
  16. package/dist/harness/steps.d.ts +101 -0
  17. package/dist/harness/steps.d.ts.map +1 -0
  18. package/dist/harness/steps.js +102 -0
  19. package/dist/harness/steps.js.map +1 -0
  20. package/dist/harness/tools/action-tools.d.ts +213 -0
  21. package/dist/harness/tools/action-tools.d.ts.map +1 -0
  22. package/dist/harness/tools/action-tools.js +724 -0
  23. package/dist/harness/tools/action-tools.js.map +1 -0
  24. package/dist/harness/tools/bridge.d.ts.map +1 -1
  25. package/dist/harness/tools/bridge.js +28 -2
  26. package/dist/harness/tools/bridge.js.map +1 -1
  27. package/dist/harness/tools/discovery.d.ts +2 -1
  28. package/dist/harness/tools/discovery.d.ts.map +1 -1
  29. package/dist/harness/tools/discovery.js +1 -1
  30. package/dist/harness/tools/discovery.js.map +1 -1
  31. package/dist/harness/tools/pending-action.d.ts +55 -0
  32. package/dist/harness/tools/pending-action.d.ts.map +1 -0
  33. package/dist/harness/tools/pending-action.js +93 -0
  34. package/dist/harness/tools/pending-action.js.map +1 -0
  35. package/dist/harness/tools/resume.d.ts +92 -0
  36. package/dist/harness/tools/resume.d.ts.map +1 -0
  37. package/dist/harness/tools/resume.js +84 -0
  38. package/dist/harness/tools/resume.js.map +1 -0
  39. package/dist/harness/tools/schema.js +29 -2
  40. package/dist/harness/tools/schema.js.map +1 -1
  41. package/dist/harness/tools/site-actions.d.ts +69 -0
  42. package/dist/harness/tools/site-actions.d.ts.map +1 -0
  43. package/dist/harness/tools/site-actions.js +310 -0
  44. package/dist/harness/tools/site-actions.js.map +1 -0
  45. package/dist/harness/tools/source.d.ts +44 -5
  46. package/dist/harness/tools/source.d.ts.map +1 -1
  47. package/dist/harness/tools/source.js +58 -9
  48. package/dist/harness/tools/source.js.map +1 -1
  49. package/dist/index.d.ts +10 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +5 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/providers/proxy.d.ts +5 -0
  54. package/dist/providers/proxy.d.ts.map +1 -1
  55. package/dist/providers/proxy.js +16 -1
  56. package/dist/providers/proxy.js.map +1 -1
  57. package/dist/providers/types.d.ts +15 -0
  58. package/dist/providers/types.d.ts.map +1 -1
  59. package/dist/types.d.ts +7 -4
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/ui/agent-fullscreen.d.ts.map +1 -1
  62. package/dist/ui/agent-fullscreen.js +771 -36
  63. package/dist/ui/agent-fullscreen.js.map +1 -1
  64. package/dist/ui/index.d.ts +2 -0
  65. package/dist/ui/index.d.ts.map +1 -1
  66. package/dist/ui/index.js +1 -0
  67. package/dist/ui/index.js.map +1 -1
  68. package/dist/ui/journey.d.ts +33 -0
  69. package/dist/ui/journey.d.ts.map +1 -0
  70. package/dist/ui/journey.js +27 -0
  71. package/dist/ui/journey.js.map +1 -0
  72. package/dist/ui/tailwind.generated.d.ts +1 -1
  73. package/dist/ui/tailwind.generated.d.ts.map +1 -1
  74. package/dist/ui/tailwind.generated.js +1 -1
  75. package/dist/ui/tailwind.generated.js.map +1 -1
  76. package/dist/webmcp-contract.d.ts +6 -2
  77. package/dist/webmcp-contract.d.ts.map +1 -1
  78. package/docs/remote-providers.md +196 -0
  79. package/docs/single-vs-multi-agent-dark.svg +78 -0
  80. package/docs/single-vs-multi-agent.svg +78 -0
  81. package/docs/turn-routing-dark.svg +52 -0
  82. package/docs/turn-routing.svg +52 -0
  83. package/package.json +3 -1
@@ -1,3 +1,3 @@
1
1
  /* GENERATED by scripts/css-to-ts.mjs — do not edit. Source: src/ui/agent.tw.css */
2
- export const TAILWIND_CSS = "/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:\"Inter\", system-ui, sans-serif;--font-mono:\"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;--color-white:#fff;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--leading-normal:1.5;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-2xl:1rem;--shadow-sm:0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-bg:#fff;--color-surface:#f5f7fb;--color-border-hair:#e4e9f2;--color-ink:#0f172a;--color-ink-2:#475569;--color-ink-3:#8a97ac;--color-blue:#2563eb;--color-blue-strong:#1d4ed8;--color-blue-tint:#eef3ff;--color-green-strong:#16a34a;--color-red:#b91c1c;--font-display:\"Manrope\", system-ui, sans-serif;--radius-card:14px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.invisible{visibility:hidden}.visible{visibility:visible}.fixed{position:fixed}.static{position:static}.sticky{position:sticky}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mt-1{margin-top:var(--spacing)}.block{display:block}.contents{display:contents}.flex{display:flex}.hidden{display:none}.inline{display:inline}.table{display:table}.w-full{width:100%}.max-w-\\[76\\%\\]{max-width:76%}.max-w-\\[420px\\]{max-width:420px}.flex-shrink{flex-shrink:1}.border-collapse{border-collapse:collapse}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.resize{resize:both}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.gap-1{gap:var(--spacing)}.gap-1\\.5{gap:calc(var(--spacing) * 1.5)}.self-end{align-self:flex-end}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-card{border-radius:var(--radius-card)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.rounded-bl-md{border-bottom-left-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-border-hair{border-color:var(--color-border-hair)}.border-red{border-color:var(--color-red)}.border-red\\/25{border-color:#b91c1c40}@supports (color:color-mix(in lab, red, red)){.border-red\\/25{border-color:color-mix(in oklab, var(--color-red) 25%, transparent)}}.bg-blue{background-color:var(--color-blue)}.bg-surface{background-color:var(--color-surface)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\\.5{padding-inline:calc(var(--spacing) * 3.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\\.5{padding-block:calc(var(--spacing) * 2.5)}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-\\[11px\\]{font-size:11px}.text-\\[13px\\]{font-size:13px}.leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-\\[0\\.14em\\]{--tw-tracking:.14em;letter-spacing:.14em}.break-words{overflow-wrap:break-word}.whitespace-pre-wrap{white-space:pre-wrap}.text-green-strong{color:var(--color-green-strong)}.text-ink{color:var(--color-ink)}.text-ink-2{color:var(--color-ink-2)}.text-red{color:var(--color-red)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-resting{--tw-shadow:0 1px 2px var(--tw-shadow-color,#0f172a0d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow\\/border{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.\\!ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}}@property --tw-rotate-x{syntax:\"*\";inherits:false}@property --tw-rotate-y{syntax:\"*\";inherits:false}@property --tw-rotate-z{syntax:\"*\";inherits:false}@property --tw-skew-x{syntax:\"*\";inherits:false}@property --tw-skew-y{syntax:\"*\";inherits:false}@property --tw-border-style{syntax:\"*\";inherits:false;initial-value:solid}@property --tw-leading{syntax:\"*\";inherits:false}@property --tw-font-weight{syntax:\"*\";inherits:false}@property --tw-tracking{syntax:\"*\";inherits:false}@property --tw-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:\"*\";inherits:false}@property --tw-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:\"*\";inherits:false}@property --tw-inset-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:\"*\";inherits:false}@property --tw-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:\"*\";inherits:false}@property --tw-inset-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:\"*\";inherits:false}@property --tw-ring-offset-width{syntax:\"<length>\";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:\"*\";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:\"*\";inherits:false;initial-value:solid}@property --tw-blur{syntax:\"*\";inherits:false}@property --tw-brightness{syntax:\"*\";inherits:false}@property --tw-contrast{syntax:\"*\";inherits:false}@property --tw-grayscale{syntax:\"*\";inherits:false}@property --tw-hue-rotate{syntax:\"*\";inherits:false}@property --tw-invert{syntax:\"*\";inherits:false}@property --tw-opacity{syntax:\"*\";inherits:false}@property --tw-saturate{syntax:\"*\";inherits:false}@property --tw-sepia{syntax:\"*\";inherits:false}@property --tw-drop-shadow{syntax:\"*\";inherits:false}@property --tw-drop-shadow-color{syntax:\"*\";inherits:false}@property --tw-drop-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:\"*\";inherits:false}@property --tw-ease{syntax:\"*\";inherits:false}";
2
+ export const TAILWIND_CSS = "/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:\"Inter\", system-ui, sans-serif;--font-mono:\"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;--color-white:#fff;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--leading-normal:1.5;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-2xl:1rem;--shadow-sm:0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-bg:#fff;--color-surface:#f5f7fb;--color-border-hair:#e4e9f2;--color-ink:#0f172a;--color-ink-2:#475569;--color-ink-3:#8a97ac;--color-blue:#2563eb;--color-blue-strong:#1d4ed8;--color-blue-tint:#eef3ff;--color-green-strong:#16a34a;--color-red:#b91c1c;--font-display:\"Manrope\", system-ui, sans-serif;--radius-card:14px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.invisible{visibility:hidden}.visible{visibility:visible}.fixed{position:fixed}.static{position:static}.sticky{position:sticky}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mt-1{margin-top:var(--spacing)}.block{display:block}.contents{display:contents}.flex{display:flex}.hidden{display:none}.inline{display:inline}.table{display:table}.w-full{width:100%}.max-w-\\[76\\%\\]{max-width:76%}.max-w-\\[420px\\]{max-width:420px}.flex-shrink{flex-shrink:1}.border-collapse{border-collapse:collapse}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.resize{resize:both}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.gap-1{gap:var(--spacing)}.gap-1\\.5{gap:calc(var(--spacing) * 1.5)}.self-end{align-self:flex-end}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-card{border-radius:var(--radius-card)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.rounded-bl-md{border-bottom-left-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-border-hair{border-color:var(--color-border-hair)}.border-red{border-color:var(--color-red)}.border-red\\/25{border-color:#b91c1c40}@supports (color:color-mix(in lab, red, red)){.border-red\\/25{border-color:color-mix(in oklab, var(--color-red) 25%, transparent)}}.bg-blue{background-color:var(--color-blue)}.bg-surface{background-color:var(--color-surface)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\\.5{padding-inline:calc(var(--spacing) * 3.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\\.5{padding-block:calc(var(--spacing) * 2.5)}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-\\[11px\\]{font-size:11px}.text-\\[13px\\]{font-size:13px}.leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-\\[0\\.14em\\]{--tw-tracking:.14em;letter-spacing:.14em}.break-words{overflow-wrap:break-word}.whitespace-pre-wrap{white-space:pre-wrap}.text-green-strong{color:var(--color-green-strong)}.text-ink{color:var(--color-ink)}.text-ink-2{color:var(--color-ink-2)}.text-red{color:var(--color-red)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-resting{--tw-shadow:0 1px 2px var(--tw-shadow-color,#0f172a0d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow\\/border{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.\\!ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}}@property --tw-rotate-x{syntax:\"*\";inherits:false}@property --tw-rotate-y{syntax:\"*\";inherits:false}@property --tw-rotate-z{syntax:\"*\";inherits:false}@property --tw-skew-x{syntax:\"*\";inherits:false}@property --tw-skew-y{syntax:\"*\";inherits:false}@property --tw-border-style{syntax:\"*\";inherits:false;initial-value:solid}@property --tw-leading{syntax:\"*\";inherits:false}@property --tw-font-weight{syntax:\"*\";inherits:false}@property --tw-tracking{syntax:\"*\";inherits:false}@property --tw-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:\"*\";inherits:false}@property --tw-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:\"*\";inherits:false}@property --tw-inset-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:\"*\";inherits:false}@property --tw-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:\"*\";inherits:false}@property --tw-inset-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:\"*\";inherits:false}@property --tw-ring-offset-width{syntax:\"<length>\";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:\"*\";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:\"*\";inherits:false;initial-value:solid}@property --tw-blur{syntax:\"*\";inherits:false}@property --tw-brightness{syntax:\"*\";inherits:false}@property --tw-contrast{syntax:\"*\";inherits:false}@property --tw-grayscale{syntax:\"*\";inherits:false}@property --tw-hue-rotate{syntax:\"*\";inherits:false}@property --tw-invert{syntax:\"*\";inherits:false}@property --tw-opacity{syntax:\"*\";inherits:false}@property --tw-saturate{syntax:\"*\";inherits:false}@property --tw-sepia{syntax:\"*\";inherits:false}@property --tw-drop-shadow{syntax:\"*\";inherits:false}@property --tw-drop-shadow-color{syntax:\"*\";inherits:false}@property --tw-drop-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:\"*\";inherits:false}@property --tw-ease{syntax:\"*\";inherits:false}";
3
3
  //# sourceMappingURL=tailwind.generated.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tailwind.generated.js","sourceRoot":"","sources":["../../src/ui/tailwind.generated.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,CAAC,MAAM,YAAY,GAAG,w0ZAAw0Z,CAAC"}
1
+ {"version":3,"file":"tailwind.generated.js","sourceRoot":"","sources":["../../src/ui/tailwind.generated.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,CAAC,MAAM,YAAY,GAAG,44ZAA44Z,CAAC"}
@@ -7,8 +7,12 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- /** The value types a property specification may declare. */
11
- export type NxisValueType = 'string' | 'number' | 'integer' | 'boolean' | 'date' | 'time';
10
+ /**
11
+ * The value types a property specification may declare.
12
+ *
13
+ * The crawler folds time inputs into `date`, and marks file inputs `file`.
14
+ */
15
+ export type NxisValueType = 'string' | 'number' | 'integer' | 'boolean' | 'date' | 'file';
12
16
  /** One choice in a property's enumerated options. */
13
17
  export interface NxisValueOption {
14
18
  '@type'?: 'nxis:ValueOption';
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp-contract.d.ts","sourceRoot":"","sources":["../src/webmcp-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1F,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,yEAAyE;AACzE,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,aAAa,CAAC;IACjC,kBAAkB,CAAC,EAAE,eAAe,EAAE,CAAC;CACxC;AAED,gEAAgE;AAChE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,2DAA2D;AAC3D,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,8BAA8B,EAAE,CAAC;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B"}
1
+ {"version":3,"file":"webmcp-contract.d.ts","sourceRoot":"","sources":["../src/webmcp-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1F,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,yEAAyE;AACzE,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,aAAa,CAAC;IACjC,kBAAkB,CAAC,EAAE,eAAe,EAAE,CAAC;CACxC;AAED,gEAAgE;AAChE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,2DAA2D;AAC3D,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,8BAA8B,EAAE,CAAC;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B"}
@@ -0,0 +1,196 @@
1
+ # Running the agent on a remote model
2
+
3
+ The harness ships two providers: `LocalGemmaProvider`, which runs Gemma 4 in the browser over WebGPU, and `createProxyProvider`, which forwards completions to an endpoint you host. This page covers the second, with a worked Claude endpoint.
4
+
5
+ You write the endpoint; the harness writes the client. What passes between them is the contract below, and it is what we keep stable.
6
+
7
+ ## The client
8
+
9
+ ```ts
10
+ import { createProxyProvider } from '@nxisai/agent-harness';
11
+
12
+ const agent = document.querySelector('nxis-agent-fullscreen');
13
+ agent.modelProvider = createProxyProvider({ endpoint: '/api/agent' });
14
+ ```
15
+
16
+ Headless, the same provider goes to the harness directly:
17
+
18
+ ```ts
19
+ const harness = new AgentHarness({
20
+ renderer,
21
+ provider: createProxyProvider({ endpoint: '/api/agent' }),
22
+ });
23
+ ```
24
+
25
+ Options: `endpoint` is required; `name` labels it in logs and debug output; `headers` adds request headers for an endpoint expecting its own auth; `fetchImpl` substitutes `fetch`, for tests or for wrapping requests with retry or instrumentation.
26
+
27
+ The provider has no `load()` and no `countTokens()`, and their absence is meaningful. No `load` tells the UI to skip the download treatment (the charging ring, the byte counts, the stage labels); no `countTokens` hides the token metrics rather than reporting zeroes. `local: false` makes the header badge read `Remote` instead of `On-device`, so a page sending conversations off-device says so.
28
+
29
+ ## The contract
30
+
31
+ Your endpoint receives a POST with `Accept: application/json` and this body:
32
+
33
+ ```ts
34
+ {
35
+ messages: ChatMessage[], // the conversation, system prompt included
36
+ tools: { name, description, inputSchema }[], // this agent's slice only
37
+ maxTokens?: number,
38
+ cache?: { stableMessages: number, toolsStable: boolean }
39
+ }
40
+ ```
41
+
42
+ It answers with:
43
+
44
+ ```ts
45
+ {
46
+ text: string, // rendered verbatim
47
+ call?: { name: string, args: object }, // at most one
48
+ usage?: { promptTokens: number, outputTokens: number },
49
+ model?: string // shown beside the input: "Claude Sonnet 5"
50
+ }
51
+ ```
52
+
53
+ `model` is optional, and worth sending. The agent shows the visitor which model is answering, and only the endpoint knows for certain — it may route between models, or fall back to a cheaper one under load. Send the name as a person should read it. Until an endpoint has answered, the element shows the `label` the provider was created with, then `name`.
54
+
55
+ Tools arrive normalised to those three fields. WebMCP carries a schema under four different keys and sometimes as a JSON string; `createProxyProvider` resolves that before sending, so an endpoint reads `inputSchema` and nothing else. It is also what keeps the request serialisable: a native WebMCP registry hands back live objects that reference the page, and stringifying one throws.
56
+
57
+ Four rules, and each one is silent when broken, which is why they are written down:
58
+
59
+ **`text` is rendered verbatim.** Strip any protocol markers in the endpoint. The harness does no parsing.
60
+
61
+ **At most one call per turn.** The harness runs one tool per turn. `createProxyProvider` takes the first and warns about the rest, including when they arrive as a `calls` array, so a mapping mistake is loud rather than invisible.
62
+
63
+ **Honour the abort.** `createProxyProvider` passes `signal` to `fetch` for you; your endpoint should likewise abandon its upstream request. Rejecting with an `AbortError` is correct: the harness treats it as a cancellation and resolves the turn as `'stopped'`, not as a failure.
64
+
65
+ **Answer with JSON while the request asks for it.** The `Accept` header is how streaming arrives later without breaking anything: a future streaming provider will ask for `text/event-stream`, and an endpoint that only speaks JSON keeps working with this one.
66
+
67
+ **The cache hint is advice.** `{ stableMessages, toolsStable }` says what the harness knows will repeat: the system prompt while the conversation stays with one agent, the tool slice while it does not change. Both go false at a specialist handoff. Whether to act on it is your backend's decision.
68
+
69
+ ## Put a proxy on your own origin
70
+
71
+ Do not call a vendor API from the browser. A key in page source is a key anyone can read, and there is no rate limit, no audit, and no CORS story. Put an endpoint on the same origin as the page:
72
+
73
+ ```
74
+ browser (provider) → POST /api/agent (your origin, session cookie) → vendor API (your key)
75
+ ```
76
+
77
+ Same origin also means your existing session auth applies, so you can answer "who is allowed to ask" before spending anything.
78
+
79
+ ## The endpoint
80
+
81
+ A Next route handler. The mapping in both directions is the part worth reading: WebMCP tool schemas go out as Claude tools, and Claude's `tool_use` block comes back as `Completion.call`.
82
+
83
+ ```ts
84
+ // app/api/agent/route.ts
85
+ import Anthropic from '@anthropic-ai/sdk';
86
+
87
+ const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
88
+
89
+ export async function POST(request: Request) {
90
+ // Your auth, your rate limiting. The browser cannot enforce either.
91
+ const { messages, tools, maxTokens, cache } = await request.json();
92
+
93
+ const system = messages.find((m) => m.role === 'system')?.content ?? '';
94
+ const turns = messages.filter((m) => m.role !== 'system');
95
+
96
+ const response = await anthropic.messages.create({
97
+ model: 'claude-sonnet-5',
98
+ max_tokens: maxTokens ?? 1024,
99
+ // The harness says when a prefix is worth caching; whether to act on it is
100
+ // yours. Claude caches from the marked block backwards.
101
+ system: cache?.toolsStable
102
+ ? [{ type: 'text', text: system, cache_control: { type: 'ephemeral' } }]
103
+ : system,
104
+ tools: tools.map((t) => ({
105
+ name: t.name,
106
+ description: t.description ?? '',
107
+ input_schema: t.inputSchema ?? { type: 'object', properties: {} },
108
+ })),
109
+ messages: turns,
110
+ });
111
+
112
+ const text = response.content
113
+ .filter((b) => b.type === 'text')
114
+ .map((b) => b.text)
115
+ .join('')
116
+ .trim();
117
+
118
+ // At most one call: the harness handles one tool per turn.
119
+ const use = response.content.find((b) => b.type === 'tool_use');
120
+
121
+ return Response.json({
122
+ text,
123
+ call: use ? { name: use.name, args: use.input } : undefined,
124
+ usage: {
125
+ promptTokens: response.usage.input_tokens,
126
+ outputTokens: response.usage.output_tokens,
127
+ },
128
+ // The model that actually answered, from the response rather than the
129
+ // request, so a fallback shows up as itself.
130
+ model: response.model,
131
+ });
132
+ }
133
+ ```
134
+
135
+ ## Writing a provider by hand
136
+
137
+ `createProxyProvider` covers the case above. Write your own `ModelProvider` when the transport is not an HTTP POST of this shape: a WebSocket, a shared worker, an SDK that manages its own connection.
138
+
139
+ ```ts
140
+ interface ModelProvider {
141
+ readonly name: string;
142
+ readonly local: boolean; // false for anything off-device
143
+ readonly ready: boolean;
144
+ readonly loading: boolean;
145
+ load?(): Promise<void>; // omit it: nothing to download
146
+ preload?(mode): void; // omit it
147
+ complete(req: CompletionRequest): Promise<Completion>;
148
+ countTokens?(text): Promise<number>; // omit it: hides the token metrics
149
+ }
150
+ ```
151
+
152
+ Implement `complete` and the four readonly fields. The four rules above still apply, and nothing enforces them for you.
153
+
154
+ ## Streaming
155
+
156
+ `onToken` is called with the whole answer so far, not the newest fragment. Nothing in the harness requires a stream: a provider that resolves once shows the thinking indicator until it does. If you want tokens as they arrive, stream from your endpoint and call `onToken` as you read.
157
+
158
+ ```ts
159
+ async complete({ messages, tools, maxTokens, signal, onToken }) {
160
+ const res = await fetch(endpoint, {
161
+ method: 'POST',
162
+ headers: { 'Content-Type': 'application/json' },
163
+ credentials: 'same-origin',
164
+ signal,
165
+ body: JSON.stringify({ messages, tools, maxTokens, stream: true }),
166
+ });
167
+ if (!res.ok || !res.body) throw new Error(`Agent endpoint returned ${res.status}`);
168
+
169
+ const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
170
+ let text = '';
171
+ let call;
172
+
173
+ for (;;) {
174
+ const { value, done } = await reader.read();
175
+ if (done) break;
176
+ for (const line of value.split('\n')) {
177
+ if (!line.startsWith('data: ')) continue;
178
+ const event = JSON.parse(line.slice(6));
179
+ if (event.type === 'text') {
180
+ text += event.delta;
181
+ onToken?.(text); // the whole answer so far
182
+ }
183
+ if (event.type === 'tool_use') call = { name: event.name, args: event.input };
184
+ }
185
+ }
186
+ return { text: text.trim(), call };
187
+ }
188
+ ```
189
+
190
+ On the endpoint, map your vendor's stream to those events. Claude's `content_block_delta` carries `text_delta` for prose, and the `tool_use` block's input arrives as `input_json_delta` fragments that have to be concatenated and parsed once the block stops.
191
+
192
+ ## Notes
193
+
194
+ The four rules under [The contract](#the-contract) are the ones that bite. One more is worth knowing when writing the endpoint:
195
+
196
+ **Tool results go back as user turns.** The harness writes them into the transcript itself, as `[nxis_query returned: …]` for a read and `[booking-form submitted with: …]` for a write. Your endpoint therefore does not need Claude's `tool_use` and `tool_result` block pairing unless you want the model to see it that way; plain turns are what arrives.
@@ -0,0 +1,78 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" color="#B0BCCF" viewBox="0 0 960 372" role="img" aria-label="Today every turn loads one system prompt and all ten tool definitions, 2476 tokens. Proposed, a coordinator with no tools routes to one of four specialists, each loading only its own prompt and tools, 506 to 611 tokens per turn.">
2
+ <defs>
3
+ <marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
4
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor"/>
5
+ </marker>
6
+ </defs>
7
+
8
+ <!-- ── TODAY ───────────────────────────────────────────── -->
9
+ <text x="0" y="14" font-family="'JetBrains Mono',monospace" font-size="11" letter-spacing="1.6" fill="currentColor" opacity=".65">SINGLE AGENT</text>
10
+
11
+ <rect x="0" y="34" width="150" height="34" rx="8" fill="none" stroke="currentColor" stroke-width="1.2" opacity=".55"/>
12
+ <text x="75" y="56" text-anchor="middle" font-family="Inter,sans-serif" font-size="12.5" fill="currentColor">user turn</text>
13
+
14
+ <line x1="75" y1="68" x2="75" y2="104" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
15
+
16
+ <rect x="0" y="106" width="380" height="128" rx="11" fill="none" stroke="currentColor" stroke-width="1.6"/>
17
+ <text x="16" y="130" font-family="Manrope,sans-serif" font-weight="700" font-size="14" fill="currentColor">One agent, every tool</text>
18
+ <text x="16" y="152" font-family="Inter,sans-serif" font-size="12" fill="currentColor" opacity=".8">system prompt · everything at once</text>
19
+ <text x="364" y="130" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="12" fill="currentColor">560</text>
20
+
21
+ <line x1="16" y1="166" x2="364" y2="166" stroke="currentColor" stroke-width="1" opacity=".3"/>
22
+ <text x="16" y="188" font-family="Inter,sans-serif" font-size="12" fill="currentColor" opacity=".8">10 tool definitions</text>
23
+ <text x="364" y="188" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="12" fill="currentColor">1,916</text>
24
+ <text x="16" y="210" font-family="Inter,sans-serif" font-size="12" fill="currentColor" opacity=".55">query · get · list · action · 6 forms</text>
25
+ <text x="364" y="216" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="13" font-weight="500" fill="currentColor">2,476 / turn</text>
26
+
27
+ <line x1="190" y1="234" x2="190" y2="268" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
28
+ <rect x="90" y="270" width="200" height="34" rx="8" fill="none" stroke="currentColor" stroke-width="1.2" opacity=".55"/>
29
+ <text x="190" y="292" text-anchor="middle" font-family="Inter,sans-serif" font-size="12.5" fill="currentColor">picks 1 of 10 tools</text>
30
+
31
+ <!-- divider -->
32
+ <line x1="452" y1="20" x2="452" y2="340" stroke="currentColor" stroke-width="1" opacity=".22" stroke-dasharray="3 5"/>
33
+
34
+ <!-- ── PROPOSED ────────────────────────────────────────── -->
35
+ <text x="516" y="14" font-family="'JetBrains Mono',monospace" font-size="11" letter-spacing="1.6" fill="#7CA6FF">SPECIALISTS</text>
36
+
37
+ <rect x="516" y="34" width="150" height="34" rx="8" fill="none" stroke="currentColor" stroke-width="1.2" opacity=".55"/>
38
+ <text x="591" y="56" text-anchor="middle" font-family="Inter,sans-serif" font-size="12.5" fill="currentColor">user turn</text>
39
+
40
+ <line x1="591" y1="68" x2="591" y2="102" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
41
+
42
+ <rect x="516" y="104" width="290" height="52" rx="10" fill="none" stroke="#7CA6FF" stroke-width="1.6"/>
43
+ <text x="532" y="126" font-family="Manrope,sans-serif" font-weight="700" font-size="13.5" fill="#7CA6FF">Coordinator</text>
44
+ <text x="532" y="145" font-family="Inter,sans-serif" font-size="11.5" fill="currentColor" opacity=".8">classify only · no tools</text>
45
+ <text x="790" y="134" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="12" fill="#7CA6FF">~120</text>
46
+
47
+ <text x="824" y="134" font-family="Inter,sans-serif" font-size="11" fill="currentColor" opacity=".6">rules first,</text>
48
+ <text x="824" y="149" font-family="Inter,sans-serif" font-size="11" fill="currentColor" opacity=".6">model only if unclear</text>
49
+
50
+ <!-- fan-out: one coordinator, four slices -->
51
+ <line x1="661" y1="156" x2="661" y2="192" stroke="currentColor" stroke-width="1.2"/>
52
+ <line x1="481" y1="192" x2="841" y2="192" stroke="currentColor" stroke-width="1.2"/>
53
+ <line x1="481" y1="192" x2="481" y2="216" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
54
+ <line x1="601" y1="192" x2="601" y2="216" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
55
+ <line x1="721" y1="192" x2="721" y2="216" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
56
+ <line x1="841" y1="192" x2="841" y2="216" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
57
+ <rect x="427" y="218" width="108" height="92" rx="10" fill="none" stroke="currentColor" stroke-width="1.4"/>
58
+ <text x="439" y="240" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">Retrieval</text>
59
+ <text x="439" y="258" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".75">query · get</text>
60
+ <text x="439" y="274" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".55">types, filters</text>
61
+ <text x="523" y="299" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="11.5" fill="currentColor">611</text>
62
+ <rect x="547" y="218" width="108" height="92" rx="10" fill="none" stroke="currentColor" stroke-width="1.4"/>
63
+ <text x="559" y="240" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">Booking</text>
64
+ <text x="559" y="258" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".75">booking · lookup</text>
65
+ <text x="559" y="274" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".55">windows, 24h line</text>
66
+ <text x="643" y="299" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="11.5" fill="currentColor">506</text>
67
+ <rect x="667" y="218" width="108" height="92" rx="10" fill="none" stroke="currentColor" stroke-width="1.4"/>
68
+ <text x="679" y="240" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">Quote</text>
69
+ <text x="679" y="258" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".75">quote · contact</text>
70
+ <text x="679" y="274" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".55">scope, no prices</text>
71
+ <text x="763" y="299" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="11.5" fill="currentColor">574</text>
72
+ <rect x="787" y="218" width="108" height="92" rx="10" fill="none" stroke="currentColor" stroke-width="1.4"/>
73
+ <text x="799" y="240" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">Forms</text>
74
+ <text x="799" y="258" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".75">plan · jobs</text>
75
+ <text x="799" y="274" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".55">tiers, roles</text>
76
+ <text x="883" y="299" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="11.5" fill="currentColor">522</text>
77
+ <text x="516" y="336" font-family="Inter,sans-serif" font-size="11.5" fill="currentColor" opacity=".65">one engine · one transcript · one tool slice loaded per turn</text>
78
+ </svg>
@@ -0,0 +1,78 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" color="#3F4B5E" viewBox="0 0 960 372" role="img" aria-label="Today every turn loads one system prompt and all ten tool definitions, 2476 tokens. Proposed, a coordinator with no tools routes to one of four specialists, each loading only its own prompt and tools, 506 to 611 tokens per turn.">
2
+ <defs>
3
+ <marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
4
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor"/>
5
+ </marker>
6
+ </defs>
7
+
8
+ <!-- ── TODAY ───────────────────────────────────────────── -->
9
+ <text x="0" y="14" font-family="'JetBrains Mono',monospace" font-size="11" letter-spacing="1.6" fill="currentColor" opacity=".65">SINGLE AGENT</text>
10
+
11
+ <rect x="0" y="34" width="150" height="34" rx="8" fill="none" stroke="currentColor" stroke-width="1.2" opacity=".55"/>
12
+ <text x="75" y="56" text-anchor="middle" font-family="Inter,sans-serif" font-size="12.5" fill="currentColor">user turn</text>
13
+
14
+ <line x1="75" y1="68" x2="75" y2="104" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
15
+
16
+ <rect x="0" y="106" width="380" height="128" rx="11" fill="none" stroke="currentColor" stroke-width="1.6"/>
17
+ <text x="16" y="130" font-family="Manrope,sans-serif" font-weight="700" font-size="14" fill="currentColor">One agent, every tool</text>
18
+ <text x="16" y="152" font-family="Inter,sans-serif" font-size="12" fill="currentColor" opacity=".8">system prompt · everything at once</text>
19
+ <text x="364" y="130" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="12" fill="currentColor">560</text>
20
+
21
+ <line x1="16" y1="166" x2="364" y2="166" stroke="currentColor" stroke-width="1" opacity=".3"/>
22
+ <text x="16" y="188" font-family="Inter,sans-serif" font-size="12" fill="currentColor" opacity=".8">10 tool definitions</text>
23
+ <text x="364" y="188" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="12" fill="currentColor">1,916</text>
24
+ <text x="16" y="210" font-family="Inter,sans-serif" font-size="12" fill="currentColor" opacity=".55">query · get · list · action · 6 forms</text>
25
+ <text x="364" y="216" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="13" font-weight="500" fill="currentColor">2,476 / turn</text>
26
+
27
+ <line x1="190" y1="234" x2="190" y2="268" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
28
+ <rect x="90" y="270" width="200" height="34" rx="8" fill="none" stroke="currentColor" stroke-width="1.2" opacity=".55"/>
29
+ <text x="190" y="292" text-anchor="middle" font-family="Inter,sans-serif" font-size="12.5" fill="currentColor">picks 1 of 10 tools</text>
30
+
31
+ <!-- divider -->
32
+ <line x1="452" y1="20" x2="452" y2="340" stroke="currentColor" stroke-width="1" opacity=".22" stroke-dasharray="3 5"/>
33
+
34
+ <!-- ── PROPOSED ────────────────────────────────────────── -->
35
+ <text x="516" y="14" font-family="'JetBrains Mono',monospace" font-size="11" letter-spacing="1.6" fill="#2563EB">SPECIALISTS</text>
36
+
37
+ <rect x="516" y="34" width="150" height="34" rx="8" fill="none" stroke="currentColor" stroke-width="1.2" opacity=".55"/>
38
+ <text x="591" y="56" text-anchor="middle" font-family="Inter,sans-serif" font-size="12.5" fill="currentColor">user turn</text>
39
+
40
+ <line x1="591" y1="68" x2="591" y2="102" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
41
+
42
+ <rect x="516" y="104" width="290" height="52" rx="10" fill="none" stroke="#2563EB" stroke-width="1.6"/>
43
+ <text x="532" y="126" font-family="Manrope,sans-serif" font-weight="700" font-size="13.5" fill="#2563EB">Coordinator</text>
44
+ <text x="532" y="145" font-family="Inter,sans-serif" font-size="11.5" fill="currentColor" opacity=".8">classify only · no tools</text>
45
+ <text x="790" y="134" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="12" fill="#2563EB">~120</text>
46
+
47
+ <text x="824" y="134" font-family="Inter,sans-serif" font-size="11" fill="currentColor" opacity=".6">rules first,</text>
48
+ <text x="824" y="149" font-family="Inter,sans-serif" font-size="11" fill="currentColor" opacity=".6">model only if unclear</text>
49
+
50
+ <!-- fan-out: one coordinator, four slices -->
51
+ <line x1="661" y1="156" x2="661" y2="192" stroke="currentColor" stroke-width="1.2"/>
52
+ <line x1="481" y1="192" x2="841" y2="192" stroke="currentColor" stroke-width="1.2"/>
53
+ <line x1="481" y1="192" x2="481" y2="216" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
54
+ <line x1="601" y1="192" x2="601" y2="216" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
55
+ <line x1="721" y1="192" x2="721" y2="216" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
56
+ <line x1="841" y1="192" x2="841" y2="216" stroke="currentColor" stroke-width="1.2" marker-end="url(#ar)"/>
57
+ <rect x="427" y="218" width="108" height="92" rx="10" fill="none" stroke="currentColor" stroke-width="1.4"/>
58
+ <text x="439" y="240" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">Retrieval</text>
59
+ <text x="439" y="258" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".75">query · get</text>
60
+ <text x="439" y="274" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".55">types, filters</text>
61
+ <text x="523" y="299" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="11.5" fill="currentColor">611</text>
62
+ <rect x="547" y="218" width="108" height="92" rx="10" fill="none" stroke="currentColor" stroke-width="1.4"/>
63
+ <text x="559" y="240" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">Booking</text>
64
+ <text x="559" y="258" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".75">booking · lookup</text>
65
+ <text x="559" y="274" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".55">windows, 24h line</text>
66
+ <text x="643" y="299" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="11.5" fill="currentColor">506</text>
67
+ <rect x="667" y="218" width="108" height="92" rx="10" fill="none" stroke="currentColor" stroke-width="1.4"/>
68
+ <text x="679" y="240" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">Quote</text>
69
+ <text x="679" y="258" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".75">quote · contact</text>
70
+ <text x="679" y="274" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".55">scope, no prices</text>
71
+ <text x="763" y="299" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="11.5" fill="currentColor">574</text>
72
+ <rect x="787" y="218" width="108" height="92" rx="10" fill="none" stroke="currentColor" stroke-width="1.4"/>
73
+ <text x="799" y="240" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">Forms</text>
74
+ <text x="799" y="258" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".75">plan · jobs</text>
75
+ <text x="799" y="274" font-family="Inter,sans-serif" font-size="10" fill="currentColor" opacity=".55">tiers, roles</text>
76
+ <text x="883" y="299" text-anchor="end" font-family="'JetBrains Mono',monospace" font-size="11.5" fill="currentColor">522</text>
77
+ <text x="516" y="336" font-family="Inter,sans-serif" font-size="11.5" fill="currentColor" opacity=".65">one engine · one transcript · one tool slice loaded per turn</text>
78
+ </svg>
@@ -0,0 +1,52 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" color="#B0BCCF" viewBox="0 0 960 268" role="img" aria-label="A turn checks a rule table first and routes with no model call. If the rules are unclear a short classification pass runs. The chosen specialist keeps handling later turns without re-routing, and can hand back once if the topic changed.">
2
+ <defs>
3
+ <marker id="ar2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
4
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor"/>
5
+ </marker>
6
+ </defs>
7
+
8
+ <rect x="0" y="60" width="120" height="42" rx="9" fill="none" stroke="currentColor" stroke-width="1.3"/>
9
+ <text x="60" y="79" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="currentColor">user turn</text>
10
+ <text x="60" y="94" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".6">+ transcript</text>
11
+
12
+ <line x1="120" y1="81" x2="168" y2="81" stroke="currentColor" stroke-width="1.3" marker-end="url(#ar2)"/>
13
+
14
+ <rect x="170" y="52" width="146" height="58" rx="9" fill="none" stroke="#7CA6FF" stroke-width="1.5"/>
15
+ <text x="243" y="74" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700" font-size="12.5" fill="#7CA6FF">rule table</text>
16
+ <text x="243" y="92" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".7">verbs, tool names</text>
17
+
18
+ <line x1="316" y1="81" x2="372" y2="81" stroke="currentColor" stroke-width="1.3" marker-end="url(#ar2)"/>
19
+ <text x="344" y="72" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".75">clear</text>
20
+
21
+ <path d="M 243 110 L 243 168" stroke="currentColor" stroke-width="1.3" fill="none" marker-end="url(#ar2)"/>
22
+ <text x="252" y="142" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".75">unclear</text>
23
+
24
+ <rect x="170" y="170" width="146" height="56" rx="9" fill="none" stroke="currentColor" stroke-width="1.3" stroke-dasharray="4 4"/>
25
+ <text x="243" y="192" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">classify pass</text>
26
+ <text x="243" y="209" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".7">~120 tok in, 1 word out</text>
27
+
28
+ <path d="M 316 198 L 400 198 L 400 116" stroke="currentColor" stroke-width="1.3" fill="none" marker-end="url(#ar2)"/>
29
+
30
+ <rect x="374" y="52" width="176" height="58" rx="9" fill="none" stroke="currentColor" stroke-width="1.6"/>
31
+ <text x="462" y="75" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700" font-size="12.5" fill="currentColor">specialist harness</text>
32
+ <text x="462" y="93" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".7">its prompt + its tools</text>
33
+
34
+ <line x1="550" y1="81" x2="606" y2="81" stroke="currentColor" stroke-width="1.3" marker-end="url(#ar2)"/>
35
+ <text x="578" y="72" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".75">turn()</text>
36
+
37
+ <rect x="608" y="52" width="150" height="58" rx="9" fill="none" stroke="currentColor" stroke-width="1.3"/>
38
+ <text x="683" y="75" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="currentColor">decide · ask · run</text>
39
+ <text x="683" y="93" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".6">the harness, as ever</text>
40
+
41
+ <line x1="758" y1="81" x2="814" y2="81" stroke="currentColor" stroke-width="1.3" marker-end="url(#ar2)"/>
42
+ <rect x="816" y="60" width="136" height="42" rx="9" fill="none" stroke="currentColor" stroke-width="1.3"/>
43
+ <text x="884" y="86" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="currentColor">rendered answer</text>
44
+
45
+ <!-- sticky loop -->
46
+ <path d="M 462 110 L 462 150 L 884 150 L 884 102" stroke="#7CA6FF" stroke-width="1.3" fill="none" marker-end="url(#ar2)" stroke-dasharray="5 4"/>
47
+ <text x="673" y="144" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="#7CA6FF">next turn stays here — no routing pass</text>
48
+
49
+ <!-- hand back -->
50
+ <path d="M 550 96 L 570 96 L 570 236 L 400 236 L 400 116" stroke="currentColor" stroke-width="1.2" fill="none" marker-end="url(#ar2)" opacity=".7"/>
51
+ <text x="485" y="252" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".7">"not mine" — moved once to the tool's owner, whose answer then stands</text>
52
+ </svg>
@@ -0,0 +1,52 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" color="#3F4B5E" viewBox="0 0 960 268" role="img" aria-label="A turn checks a rule table first and routes with no model call. If the rules are unclear a short classification pass runs. The chosen specialist keeps handling later turns without re-routing, and can hand back once if the topic changed.">
2
+ <defs>
3
+ <marker id="ar2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
4
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor"/>
5
+ </marker>
6
+ </defs>
7
+
8
+ <rect x="0" y="60" width="120" height="42" rx="9" fill="none" stroke="currentColor" stroke-width="1.3"/>
9
+ <text x="60" y="79" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="currentColor">user turn</text>
10
+ <text x="60" y="94" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".6">+ transcript</text>
11
+
12
+ <line x1="120" y1="81" x2="168" y2="81" stroke="currentColor" stroke-width="1.3" marker-end="url(#ar2)"/>
13
+
14
+ <rect x="170" y="52" width="146" height="58" rx="9" fill="none" stroke="#2563EB" stroke-width="1.5"/>
15
+ <text x="243" y="74" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700" font-size="12.5" fill="#2563EB">rule table</text>
16
+ <text x="243" y="92" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".7">verbs, tool names</text>
17
+
18
+ <line x1="316" y1="81" x2="372" y2="81" stroke="currentColor" stroke-width="1.3" marker-end="url(#ar2)"/>
19
+ <text x="344" y="72" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".75">clear</text>
20
+
21
+ <path d="M 243 110 L 243 168" stroke="currentColor" stroke-width="1.3" fill="none" marker-end="url(#ar2)"/>
22
+ <text x="252" y="142" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".75">unclear</text>
23
+
24
+ <rect x="170" y="170" width="146" height="56" rx="9" fill="none" stroke="currentColor" stroke-width="1.3" stroke-dasharray="4 4"/>
25
+ <text x="243" y="192" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700" font-size="12" fill="currentColor">classify pass</text>
26
+ <text x="243" y="209" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".7">~120 tok in, 1 word out</text>
27
+
28
+ <path d="M 316 198 L 400 198 L 400 116" stroke="currentColor" stroke-width="1.3" fill="none" marker-end="url(#ar2)"/>
29
+
30
+ <rect x="374" y="52" width="176" height="58" rx="9" fill="none" stroke="currentColor" stroke-width="1.6"/>
31
+ <text x="462" y="75" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700" font-size="12.5" fill="currentColor">specialist harness</text>
32
+ <text x="462" y="93" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".7">its prompt + its tools</text>
33
+
34
+ <line x1="550" y1="81" x2="606" y2="81" stroke="currentColor" stroke-width="1.3" marker-end="url(#ar2)"/>
35
+ <text x="578" y="72" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".75">turn()</text>
36
+
37
+ <rect x="608" y="52" width="150" height="58" rx="9" fill="none" stroke="currentColor" stroke-width="1.3"/>
38
+ <text x="683" y="75" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="currentColor">decide · ask · run</text>
39
+ <text x="683" y="93" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".6">the harness, as ever</text>
40
+
41
+ <line x1="758" y1="81" x2="814" y2="81" stroke="currentColor" stroke-width="1.3" marker-end="url(#ar2)"/>
42
+ <rect x="816" y="60" width="136" height="42" rx="9" fill="none" stroke="currentColor" stroke-width="1.3"/>
43
+ <text x="884" y="86" text-anchor="middle" font-family="Inter,sans-serif" font-size="12" fill="currentColor">rendered answer</text>
44
+
45
+ <!-- sticky loop -->
46
+ <path d="M 462 110 L 462 150 L 884 150 L 884 102" stroke="#2563EB" stroke-width="1.3" fill="none" marker-end="url(#ar2)" stroke-dasharray="5 4"/>
47
+ <text x="673" y="144" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="#2563EB">next turn stays here — no routing pass</text>
48
+
49
+ <!-- hand back -->
50
+ <path d="M 550 96 L 570 96 L 570 236 L 400 236 L 400 116" stroke="currentColor" stroke-width="1.2" fill="none" marker-end="url(#ar2)" opacity=".7"/>
51
+ <text x="485" y="252" text-anchor="middle" font-family="Inter,sans-serif" font-size="10.5" fill="currentColor" opacity=".7">"not mine" — moved once to the tool's owner, whose answer then stands</text>
52
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxisai/agent-harness",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "An on-device agent for the browser: a tool-calling harness over WebMCP and remote MCP, plus a drop-in chat UI. Decides whether to run a tool, ask for what's missing, or confirm before writing.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,6 +25,8 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
+ "docs/remote-providers.md",
29
+ "docs/*.svg",
28
30
  "README.md",
29
31
  "LICENSE"
30
32
  ],