@cerebro-labs/agentnava-sdk 0.26.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 (67) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +12 -0
  3. package/README.md +230 -0
  4. package/agent.d.ts +285 -0
  5. package/agent.js +394 -0
  6. package/agent.js.map +1 -0
  7. package/attach.d.ts +49 -0
  8. package/attach.js +115 -0
  9. package/attach.js.map +1 -0
  10. package/connection-status.d.ts +28 -0
  11. package/connection-status.js +61 -0
  12. package/connection-status.js.map +1 -0
  13. package/connection.d.ts +89 -0
  14. package/connection.js +143 -0
  15. package/connection.js.map +1 -0
  16. package/conversation.d.ts +177 -0
  17. package/conversation.js +348 -0
  18. package/conversation.js.map +1 -0
  19. package/end-user.d.ts +159 -0
  20. package/end-user.js +209 -0
  21. package/end-user.js.map +1 -0
  22. package/errors.d.ts +43 -0
  23. package/errors.js +55 -0
  24. package/errors.js.map +1 -0
  25. package/http.d.ts +32 -0
  26. package/http.js +192 -0
  27. package/http.js.map +1 -0
  28. package/import-tools.d.ts +282 -0
  29. package/import-tools.js +686 -0
  30. package/import-tools.js.map +1 -0
  31. package/index.d.ts +16 -0
  32. package/index.js +38 -0
  33. package/index.js.map +1 -0
  34. package/knowledge-base.d.ts +165 -0
  35. package/knowledge-base.js +274 -0
  36. package/knowledge-base.js.map +1 -0
  37. package/load-agent-dir.d.ts +17 -0
  38. package/load-agent-dir.js +192 -0
  39. package/load-agent-dir.js.map +1 -0
  40. package/package.json +32 -0
  41. package/project.d.ts +94 -0
  42. package/project.js +155 -0
  43. package/project.js.map +1 -0
  44. package/secret.d.ts +45 -0
  45. package/secret.js +69 -0
  46. package/secret.js.map +1 -0
  47. package/templates-inline.d.ts +2 -0
  48. package/templates-inline.js +16 -0
  49. package/templates-inline.js.map +1 -0
  50. package/templates.d.ts +16 -0
  51. package/templates.js +32 -0
  52. package/templates.js.map +1 -0
  53. package/turn.d.ts +11 -0
  54. package/turn.js +244 -0
  55. package/turn.js.map +1 -0
  56. package/types.d.ts +485 -0
  57. package/types.js +4 -0
  58. package/types.js.map +1 -0
  59. package/version.d.ts +1 -0
  60. package/version.js +5 -0
  61. package/version.js.map +1 -0
  62. package/workflows.d.ts +36 -0
  63. package/workflows.js +101 -0
  64. package/workflows.js.map +1 -0
  65. package/workspace.d.ts +290 -0
  66. package/workspace.js +390 -0
  67. package/workspace.js.map +1 -0
package/agent.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,0DAA0D;;AAE1D,OAAO,EAAmB,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtG,OAAO,EAAE,kBAAkB,EAAuB,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAyGjD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,KAAK;IACR,EAAE,CAAS;IACX,KAAK,CAAO;IACb,QAAQ,CAAa;IAE7B,YAAY,IAAU,EAAE,EAAU,EAAE,QAAoB;QACvD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED,IAAY,IAAI;QACf,OAAO,cAAc,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACpD,CAAC;IAED;uEACmE;IACnE,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,UAAgC,EAAE;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAChC,IAAI,CAAC,IAAI,EACT,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CACxE,CAAC;QACF,2EAA2E;QAC3E,sEAAsE;QACtE,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACxD,OAAO,IAAI,CAAC;IACb,CAAC;IAED,mCAAmC;IACnC,KAAK,CAAC,QAAQ;QACb,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,GAAG,IAAI,CAAC,IAAI,WAAW,CAAC,CAAC;QACrF,OAAO,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,MAAM,CAAC,MAAmB;QAC/B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,GAAG,IAAI,CAAC,IAAI,WAAW,EAAE,MAAM,CAAC,CAAC;QAC/E,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,EAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,MAAM;QACX,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,4EAA4E;IAE5E;;;;;;;OAOG;IACH,KAAK,CAAC,WAAW,CAAC,QAAkB;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC;QACzE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,IAAY;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC;QACzE,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED;8EAC0E;IAC1E,KAAK,CAAC,WAAW,CAAC,QAAmB;QACpC,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,4EAA4E;IAE5E;;;;;;;;OAQG;IACM,SAAS,GAAG;QACpB,GAAG,EAAE,KAAK,EAAE,IAAY,EAAE,KAA+B,EAAE,WAAoB,EAAsB,EAAE,CACtG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAY,GAAG,IAAI,CAAC,IAAI,kBAAkB,EAAE;YAC7D,MAAM,EAAE,KAAK;YACb,KAAK;YACL,KAAK,EAAE,EAAE,IAAI,EAAE;YACf,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;SACnC,CAAC;QACH,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,GAAG,EAAwB,EAAE;YAChD,oEAAoE;YACpE,yEAAyE;YACzE,uEAAuE;YACvE,oEAAoE;YACpE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,GAAG,IAAI,CAAC,IAAI,kBAAkB,CAC9B,CAAC;YACF,MAAM,MAAM,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QACpE,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,IAAY,EAAiB,EAAE;YAC7C,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,kBAAkB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,CAAC;KACD,CAAC;IAEF,2EAA2E;IAE3E;;;;;;;;;;;;;;;OAeG;IACM,cAAc,GAAG;QACzB;;;;;;;;;;WAUG;QACH,MAAM,EAAE,KAAK,EAAE,GAAW,EAAkB,EAAE;YAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,GAAG,IAAI,CAAC,IAAI,kBAAkB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YACtF,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YACpB,OAAO,IAAI,CAAC;QACb,CAAC;QAED;;uBAEe;QACf,MAAM,EAAE,KAAK,EAAE,GAAW,EAAkB,EAAE;YAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAClC,GAAG,IAAI,CAAC,IAAI,oBAAoB,kBAAkB,CAAC,GAAG,CAAC,EAAE,CACzD,CAAC;YACF,IAAI,GAAG;gBAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YAC7B,OAAO,IAAI,CAAC;QACb,CAAC;QAED,mEAAmE;QACnE,IAAI,EAAE,KAAK,IAAuB,EAAE;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACpD,OAAO,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC;QACrC,CAAC;KACD,CAAC;IAEF,4EAA4E;IAE5E;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,cAAc,CAAC,QAAkB;QACtC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CACjC,aAAa,EACb,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CACpB,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;YACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;YAChE,IAAI,QAAQ;gBAAE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;;gBAC7F,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9E,CAAC;QACD,KAAK,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,CAAC;YACnF,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,WAAW,EAAE;gBAC9C,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE;aAChD,CAAC,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,EAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,KAAK,CAAC,MAAM,CAAC,IAA0C;QACtD,IAAI,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC,cAAc,CAAC,IAAgB,CAAC,CAAC;QACnE,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAiC,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACzD,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,cAAc,CAAC;YAC1B,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;YACnC,GAAG,CAAC,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC;SAC/B,CAAC,CAAC;IACJ,CAAC;IAED;;;;4DAIwD;IACxD,KAAK,CAAC,WAAW;QAChB,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,2EAA2E;QAC3E,6DAA6D;QAC7D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,GAAG,IAAI,CAAC,IAAI,cAAc,CAC1B,CAAC;QACF,OAAO,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,SAAS,CAAC,QAAgB;QAC/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAChC,yBAAyB,EACzB,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CAC9B,CAAC;QACF,yEAAyE;QACzE,2DAA2D;QAC3D,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;IAC7D,CAAC;IAED;;;;uDAImD;IACnD,KAAK,CAAC,UAAU,CAAC,QAAgB;QAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoC,iBAAiB,EAAE;YACtF,OAAO,EAAE,IAAI,CAAC,EAAE;SAChB,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;aACnC,GAAG,CAAC,kBAAkB,CAAC;aACvB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC;QACtD,qEAAqE;QACrE,uBAAuB;QACvB,IAAI,CAAC,KAAK,EAAE,EAAE;YAAE,OAAO;QACvB,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;IACjF,CAAC;IAED,4EAA4E;IAE5E,0EAA0E;IAC1E,KAAK,CAAC,KAAK,CAAC,OAAqB;QAChC,yEAAyE;QACzE,sEAAsE;QACtE,uEAAuE;QACvE,uEAAuE;QACvE,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACzD,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnD,MAAM,QAAQ,GACb,OAAO,CAAC,OAAO,IAAI,WAAW,IAAI,SAAS;YAC1C,CAAC,CAAC;gBACA,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpD,GAAG,CAAC,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC;gBAC/B,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;aACnC;YACF,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,mBAAmB,EAAE;YAC9E,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClE,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;YAC7D,uEAAuE;YACvE,EAAE;YACF,6DAA6D;YAC7D,uEAAuE;YACvE,qEAAqE;YACrE,qEAAqE;YACrE,6CAA6C;YAC7C,EAAE;YACF,sEAAsE;YACtE,sEAAsE;YACtE,qEAAqE;YACrE,wCAAwC;YACxC,GAAG,CAAC,QAAQ,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;YAC9C,uEAAuE;YACvE,gEAAgE;YAChE,qEAAqE;YACrE,0CAA0C;YAC1C,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;YACrE,wDAAwD;YACxD,EAAE;YACF,oEAAoE;YACpE,oEAAoE;YACpE,uEAAuE;YACvE,qEAAqE;YACrE,sEAAsE;YACtE,mEAAmE;YACnE,sCAAsC;YACtC,EAAE;YACF,+DAA+D;YAC/D,kEAAkE;YAClE,KAAK,EAAE,IAAI;SACX,CAAC,CAAC;QACH,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACpD,CAAC;IAED;;;;;;;;;;OAUG;IACH,YAAY,CAAC,cAAsB;QAClC,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;IACrD,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,KAAK,CAAC,aAAa,CAAC,UAAgD,EAAE;QACrE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6C,mBAAmB,EAAE;YACjG,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;YAC5D,sEAAsE;YACtE,kEAAkE;YAClE,6DAA6D;YAC7D,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;SACtE,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;CACD;;AAED;0CAC0C","sourcesContent":["// An agent: a configuration you start conversations from.\n\nimport { type Attachable, isBindings, providersOf, secretIdsOf, splitAttachments } from './attach.ts';\nimport type { Connection } from './connection.ts';\nimport { toConnectionStatus, type WireConnection } from './connection-status.ts';\nimport { Conversation } from './conversation.ts';\nimport type { Http } from './http.ts';\nimport type {\n\tAgentConversationData,\n\tAgentData,\n\tAgentFields,\n\tBindings,\n\tConnectionStatus,\n\tFileEntry,\n\tTrigger,\n\tWorkflow,\n} from './types.ts';\n\n/**\n * Turn the accounts a caller is holding into the `{ provider: id }` map the\n * wire speaks.\n *\n * Shared by `start`, `agent.attach` and `conversation.attach` so the three\n * cannot drift. A handle you created carries its provider already; one you\n * addressed by id has to read it, which is the only request here and only when\n * it is unavoidable.\n */\nexport interface StartOptions {\n\t/**\n\t * Accounts to attach AS THE CONVERSATION STARTS.\n\t *\n\t * The common shape for an application with many end users: the person\n\t * connected Gmail once, and this thread should act as them from its first\n\t * turn.\n\t *\n\t * ```ts\n\t * const gmail = await ws.endUser(row.userId).connections.list();\n\t * const c = await agent.start({ endUserId: row.userId, attach: gmail });\n\t * ```\n\t *\n\t * Doing it here rather than with a second `conversation.attach()` call\n\t * closes a real window: between `start` and the attach, the conversation\n\t * exists and can be asked something, and it would answer without the\n\t * account.\n\t *\n\t * STILL EXPLICIT. Naming `endUserId` does not attach their accounts; this\n\t * line does, and you can read it.\n\t *\n\t * TAKES SECRETS TOO. A `Secret` from `ws.endUser(id).secrets.create(...)`\n\t * attaches by id, which is the only way a stored per-person credential can\n\t * reach a turn: you never hold its value. Mix them freely, they are split by\n\t * type before the launch.\n\t */\n\tattach?: Attachable | Attachable[];\n\n\t/**\n\t * Secret VALUES for this conversation only.\n\t *\n\t * Not `attach`, because a secret has no id you can hand over: binding one\n\t * takes the value itself. That asymmetry is in the product, not in this\n\t * naming, and hiding it behind the same word would not remove it.\n\t */\n\tsecrets?: Record<string, string>;\n\n\t/** Pin a specific version. Defaults to the latest at the moment of starting.\n\t *\n\t * A conversation pins its version for life: publishing afterwards does not\n\t * move a conversation that is already open. */\n\tversion?: number;\n\tparameters?: Record<string, string | number>;\n\n\t/**\n\t * WHO this conversation is for: one of YOUR users, not one of ours.\n\t *\n\t * REQUIRED, and nullable. You must say, and `null` is a real answer: a\n\t * nightly report belongs to nobody. Optional would let it be forgotten,\n\t * which is the mistake worth designing against, because a conversation\n\t * without an owner cannot be listed back to the person it was for.\n\t *\n\t * It is a LABEL on the conversation, and nothing else. It does not decide\n\t * which conversation you get, because `start` always starts a new one.\n\t * Later you can ask for everything belonging to that person:\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: row.userId });\n\t * const nobody = await agent.start({ endUserId: null }); // a nightly job\n\t * const theirs = await agent.conversations({ endUser: row.userId });\n\t * ```\n\t *\n\t * To CONTINUE a conversation, keep its id and address it directly. That is\n\t * the only way to continue one, on purpose:\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('and the rest?');\n\t * ```\n\t *\n\t * The id is YOURS and opaque to us: a row id from your database, not an\n\t * email and not something we authenticate. We never verify it, so it is\n\t * exactly as trustworthy as whatever holds your API key.\n\t *\n\t * NOT A WORKSPACE MEMBER'S ID. `ws.members()` lists the colleagues who share\n\t * this account, and their `userId` is a platform id we issued. This field is\n\t * a different namespace entirely. Both were once spelled `userId`, so\n\t * passing a member id here was a call that could not fail: there is nothing\n\t * for an end-user id to be checked against, so it just labelled the\n\t * conversation with a colleague's internal id and looked fine.\n\t */\n\tendUserId: string | null;\n}\n\n/**\n * An agent you can configure and start conversations from.\n *\n * Holding one makes NO request: `ws.agent(id)` is free and the first call on it\n * is the first round trip.\n *\n * EVERY METHOD THAT CHANGES THE AGENT PUBLISHES A VERSION. There is no separate\n * save. What you do not send is inherited from the current version, which is the\n * only reason changing one field is safe: a resend that rebuilt the whole\n * configuration would drop whatever the caller forgot, and that bug has shipped\n * here four separate times.\n */\nexport class Agent {\n\treadonly id: string;\n\treadonly #http: Http;\n\tprivate snapshot?: AgentData;\n\n\tconstructor(http: Http, id: string, snapshot?: AgentData) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/agents/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/** The last-known configuration, from whatever call produced this object.\n\t * Undefined on an agent you addressed by id and have not read. */\n\tget data(): AgentData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\tasync get(options: { version?: number } = {}): Promise<AgentData> {\n\t\tconst data = await this.#http.get<AgentData>(\n\t\t\tthis.base,\n\t\t\toptions.version !== undefined ? { version: options.version } : undefined,\n\t\t);\n\t\t// Only cache the CURRENT configuration. Caching a historical version would\n\t\t// make `agent.data` mean different things depending on the last call.\n\t\tif (options.version === undefined) this.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/** Every version, newest first. */\n\tasync versions(): Promise<AgentData[]> {\n\t\tconst res = await this.#http.get<{ versions: AgentData[] }>(`${this.base}/versions`);\n\t\treturn res.versions ?? [];\n\t}\n\n\t/** Publish a new version with these fields changed and everything else kept. */\n\tasync update(fields: AgentFields): Promise<Agent> {\n\t\tconst data = await this.#http.post<AgentData>(`${this.base}/versions`, fields);\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t// --------------------------------------------------------------- workflows\n\n\t/**\n\t * Add a workflow, or replace the one with the same name.\n\t *\n\t * Reads the current list and sends it back with this one in place, because\n\t * the API replaces the whole list: a workflow set is a list, and there is no\n\t * \"merge one item\" that does not have to decide what a duplicate name means.\n\t * Deciding it here, once, is better than every caller deciding it.\n\t */\n\tasync setWorkflow(workflow: Workflow): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\tconst next = current.filter((w) => w.name !== workflow.name).concat(workflow);\n\t\treturn this.update({ workflows: next });\n\t}\n\n\tasync removeWorkflow(name: string): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\treturn this.update({ workflows: current.filter((w) => w.name !== name) });\n\t}\n\n\t/** Replace the trigger list. Sending `[]` removes every trigger, including\n\t * `chat`, which is what leaves an agent that only runs on a schedule. */\n\tasync setTriggers(triggers: Trigger[]): Promise<Agent> {\n\t\treturn this.update({ triggers });\n\t}\n\n\t// --------------------------------------------------------------- knowledge\n\n\t/**\n\t * The agent's knowledge: files every conversation can read.\n\t *\n\t * BY REFERENCE, not by copy. A conversation reads the knowledge as it is now,\n\t * so replacing a file changes what open conversations see on their next turn.\n\t * That is the opposite of how a version works, and it is deliberate: a price\n\t * list that could not be corrected without republishing the agent would be\n\t * useless.\n\t */\n\treadonly knowledge = {\n\t\tput: async (path: string, bytes: Uint8Array | ArrayBuffer, contentType?: string): Promise<FileEntry> =>\n\t\t\tthis.#http.request<FileEntry>(`${this.base}/knowledge/files`, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbytes,\n\t\t\t\tquery: { path },\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t}),\n\t\tlist: async (path = '/'): Promise<FileEntry[]> => {\n\t\t\t// The route returns EVERY knowledge file for the agent; it takes no\n\t\t\t// directory parameter. Filtering here rather than sending one the server\n\t\t\t// ignores, because a call that looks filtered and is not is worse than\n\t\t\t// no filter at all. Knowledge sets are small, so one read is right.\n\t\t\tconst res = await this.#http.get<{ files: (FileEntry & { path: string })[] }>(\n\t\t\t\t`${this.base}/knowledge/files`,\n\t\t\t);\n\t\t\tconst prefix = path === '/' ? '/' : `${path.replace(/\\/+$/, '')}/`;\n\t\t\treturn (res.files ?? []).filter((f) => f.path?.startsWith(prefix));\n\t\t},\n\t\tremove: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/knowledge/files`, { path });\n\t\t},\n\t};\n\n\t// -------------------------------------------------------- knowledge bases\n\n\t/**\n\t * The WORKSPACE knowledge bases this agent reads.\n\t *\n\t * Different from `knowledge` above, and the distinction is the whole point.\n\t * `agent.knowledge` is files this agent owns; nobody else has them. A\n\t * knowledge base is shared: one price list, attached to nine agents, corrected\n\t * in one place.\n\t *\n\t * Each attached base is mounted read-only at `/knowledge/<key>/` when a turn\n\t * starts, so the agent reads it with `ls`, `grep` and `cat`.\n\t *\n\t * ATTACHING PUBLISHES A VERSION. Which bases an agent reads is part of its\n\t * configuration, so the change is diffable and reversible like any other\n\t * configuration change. The CONTENTS of a base are not versioned: correcting\n\t * a file changes what every attached agent sees on its next turn.\n\t */\n\treadonly knowledgeBases = {\n\t\t/**\n\t\t * Attach one base by key.\n\t\t *\n\t\t * One call, not read-modify-write, deliberately. Two attaches racing\n\t\t * through a read of the current list and a write of the whole list would\n\t\t * lose one of them silently, and losing an attachment looks exactly like\n\t\t * an agent that cannot find its knowledge. The server appends.\n\t\t *\n\t\t * Attaching a key that names no base is refused, naming the key. Attaching\n\t\t * one that is already attached is a no-op, not an error.\n\t\t */\n\t\tattach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/knowledge-bases`, { key });\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Detach one base. The base itself and its files are untouched: this only\n\t\t * stops THIS agent from reading it. Detaching one that is not attached is\n\t\t * a no-op. */\n\t\tdetach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(\n\t\t\t\t`${this.base}/knowledge-bases/${encodeURIComponent(key)}`,\n\t\t\t);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** The keys this agent currently reads, in configuration order. */\n\t\tlist: async (): Promise<string[]> => {\n\t\t\tconst current = this.snapshot ?? (await this.get());\n\t\t\treturn current.knowledgeBases ?? [];\n\t\t},\n\t};\n\n\t// ------------------------------------------------- credentials, agent tier\n\n\t/**\n\t * Supply the credentials declared at 'fixed' scope: set once, used by every\n\t * conversation. This does NOT satisfy a declaration scoped 'conversation'.\n\t *\n\t * A secret is written to this agent's own scope, so it is readable by this\n\t * agent and nothing else in the workspace, and it overrides a workspace\n\t * secret of the same name. Writing one twice replaces the value rather than\n\t * failing, because `bind` means \"this is the credential now\".\n\t *\n\t * A connection value is the id of an account already attached to the\n\t * workspace; use `authorize()` when there is no account yet, since that is\n\t * the flow where a person signs in and no token passes through your code.\n\t *\n\t * This used to POST the whole `{secrets, connections}` object to\n\t * `/v1/agents/:id/bindings`, which answers only to `{kind, decl}` and records\n\t * a DECLARATION, never a value. So the call 400'd, and had it not, it would\n\t * have stored the fact that a secret is wanted rather than the secret.\n\t */\n\tasync #applyBindings(bindings: Bindings): Promise<Agent> {\n\t\tfor (const [name, value] of Object.entries(bindings.secrets ?? {})) {\n\t\t\tconst mine = (await this.#http.get<{ secrets: { id: string; name: string; agentId?: string | null }[] }>(\n\t\t\t\t'/v1/secrets',\n\t\t\t\t{ agentId: this.id },\n\t\t\t)).secrets ?? [];\n\t\t\tconst existing = mine.find((s) => s.name === name && s.agentId);\n\t\t\tif (existing) await this.#http.patch(`/v1/secrets/${encodeURIComponent(existing.id)}`, { value });\n\t\t\telse await this.#http.post('/v1/secrets', { name, value, agentId: this.id });\n\t\t}\n\t\tfor (const [provider, connectionId] of Object.entries(bindings.connections ?? {})) {\n\t\t\tawait this.#http.post(`${this.base}/bindings`, {\n\t\t\t\tkind: 'connection',\n\t\t\t\tdecl: { provider, connectionId, scope: 'fixed' },\n\t\t\t});\n\t\t}\n\t\tconst data = await this.get();\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\t/**\n\t * Give this agent a credential, so every conversation with it can use one.\n\t *\n\t * ```ts\n\t * await agent.attach(gmail); // one account\n\t * await agent.attach([gmail, calendar]); // several\n\t * await agent.attach({ secrets: { KEY: value } }); // a secret VALUE\n\t * ```\n\t *\n\t * ONE VERB, NOT TWO. This and `bind` used to both exist and do the same job\n\t * through different shapes, which is how a surface becomes hard to learn:\n\t * two names for one idea means every reader has to work out whether the\n\t * difference is meaningful. It is not, so `bind` is gone.\n\t *\n\t * \"Attach\" rather than \"bind\" because it is the plainer word, it is the one\n\t * that pairs with `detach`, and `knowledgeBases.attach/detach` already uses\n\t * it for the same shape of operation.\n\t *\n\t * ACCOUNTS TAKE THE OBJECT, SECRETS TAKE A VALUE, and that asymmetry is in\n\t * the product rather than in the naming. An account has an id you can hand\n\t * over; a secret does not, because binding one means supplying the value\n\t * itself. Hiding that behind one shape would not make it go away.\n\t *\n\t * The counterpart to `authorize()`, which creates an account AND attaches it\n\t * in one step. Use this when the account already exists: one team mailbox\n\t * connected once and attached to nine agents, rather than nine sign-ins.\n\t */\n\tasync attach(what: Attachable | Attachable[] | Bindings): Promise<Agent> {\n\t\tif (isBindings(what)) return this.#applyBindings(what as Bindings);\n\t\tconst split = splitAttachments(what as Attachable | Attachable[]);\n\t\tconst connections = await providersOf(split.connections);\n\t\tconst secretIds = await secretIdsOf(split.secrets);\n\t\treturn this.#applyBindings({\n\t\t\t...(connections && { connections }),\n\t\t\t...(secretIds && { secretIds }),\n\t\t});\n\t}\n\n\t/** What each declared provider's actual state is: DECLARED IS NOT CONNECTED.\n\t *\n\t * An agent can require Gmail with nobody signed in, and that is the state a\n\t * setup checklist has to show. Without this the only way to find the gap is\n\t * to start a conversation and watch the agent fail. */\n\tasync connections(): Promise<ConnectionStatus[]> {\n\t\t// The MERGED view, not the raw connection rows. /v1/connections?agentId=\n\t\t// lists rows that exist, so a provider the agent declares and nobody has\n\t\t// connected is simply absent from it, which is the one case this method is\n\t\t// most often asked about. The server merges declared against live with the\n\t\t// same function the agent's own check_connections tool uses.\n\t\tconst res = await this.#http.get<{ connections: ConnectionStatus[] }>(\n\t\t\t`${this.base}/connections`,\n\t\t);\n\t\treturn res.connections ?? [];\n\t}\n\n\t/**\n\t * Attach a third-party account to this agent, at `fixed` scope: one account,\n\t * used by every conversation.\n\t *\n\t * Returns a url to send a person to. They sign in there, in their own\n\t * browser, and the account attaches when they finish. YOU NEVER SEE A TOKEN,\n\t * which is the entire point: nobody should be pasting a Gmail credential into\n\t * a config field.\n\t *\n\t * This does NOT satisfy a declaration scoped 'conversation'. Use\n\t * `conversation.authorize()` for those, which attaches the account to that\n\t * one conversation and cannot overwrite this shared one.\n\t */\n\tasync authorize(provider: string): Promise<{ url: string; connectionId: string }> {\n\t\tconst res = await this.#http.post<{ authUrl: string; connectionId: string }>(\n\t\t\t'/v1/connections/connect',\n\t\t\t{ provider, agentId: this.id },\n\t\t);\n\t\t// `url` on the way out, `authUrl` on the wire. The SDK's name is the one\n\t\t// the reference documents and reads better at a call site.\n\t\treturn { url: res.authUrl, connectionId: res.connectionId };\n\t}\n\n\t/** Detach the account connected for this provider.\n\t *\n\t * Reads the raw ROWS rather than the merged status, because revoking needs\n\t * the connection's id and the merged view is keyed by provider: it answers\n\t * \"is Gmail connected\", not \"which row is it\". */\n\tasync disconnect(provider: string): Promise<void> {\n\t\tconst res = await this.#http.get<{ connections: WireConnection[] }>('/v1/connections', {\n\t\t\tagentId: this.id,\n\t\t});\n\t\tconst match = (res.connections ?? [])\n\t\t\t.map(toConnectionStatus)\n\t\t\t.find((c) => c.provider === provider && c.connected);\n\t\t// Nothing connected is not an error: the caller wanted this provider\n\t\t// detached, and it is.\n\t\tif (!match?.id) return;\n\t\tawait this.#http.post(`/v1/connections/${encodeURIComponent(match.id)}/revoke`);\n\t}\n\n\t// ----------------------------------------------------------- conversations\n\n\t/** Start a conversation. It pins the agent's version now and keeps it. */\n\tasync start(options: StartOptions): Promise<Conversation> {\n\t\t// Resolved BEFORE the launch, because an account is named on the wire by\n\t\t// its provider and a handle addressed by id may not know its own yet.\n\t\t// Split first: a Connection is named on the wire by its PROVIDER and a\n\t\t// Secret by the declared NAME it fills, so the two cannot share a map.\n\t\tconst split = splitAttachments(options.attach);\n\t\tconst connections = await providersOf(split.connections);\n\t\tconst secretIds = await secretIdsOf(split.secrets);\n\t\tconst bindings =\n\t\t\toptions.secrets || connections || secretIds\n\t\t\t\t? {\n\t\t\t\t\t\t...(options.secrets && { secrets: options.secrets }),\n\t\t\t\t\t\t...(secretIds && { secretIds }),\n\t\t\t\t\t\t...(connections && { connections }),\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\t\tconst data = await this.#http.post<AgentConversationData>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.version !== undefined && { version: options.version }),\n\t\t\t...(options.parameters && { parameters: options.parameters }),\n\t\t\t// `sessionBindings`, NOT `secrets` and `connections` at the top level.\n\t\t\t//\n\t\t\t// This sent them at the top level and the launch route reads\n\t\t\t// `body.sessionBindings`, so they were dropped in silence: the route's\n\t\t\t// own body type has no top-level `secrets` or `connections` to read.\n\t\t\t// A conversation started with a credential began without it, and the\n\t\t\t// first turn failed somewhere else entirely.\n\t\t\t//\n\t\t\t// Third place this exact envelope mistake was found in one afternoon,\n\t\t\t// after `agent.bind` and `conversation.bind`. The shape of the bug is\n\t\t\t// always the same: the SDK agrees with itself, the route agrees with\n\t\t\t// itself, and nothing compares the two.\n\t\t\t...(bindings && { sessionBindings: bindings }),\n\t\t\t// `!= null`, catching undefined as well as null. The field is REQUIRED\n\t\t\t// in TypeScript, but a JavaScript caller can still omit it, and\n\t\t\t// `!== null` alone then sent `user: {}`: an owner object with no id,\n\t\t\t// which is neither \"nobody\" nor a person.\n\t\t\t...(options.endUserId != null && { user: { id: options.endUserId } }),\n\t\t\t// ALWAYS a new conversation, even when a user is named.\n\t\t\t//\n\t\t\t// The route resumes that user's most recent conversation when it is\n\t\t\t// given an identity and not told otherwise. That default exists for\n\t\t\t// our own web app, where a page reload must not leave a trail of empty\n\t\t\t// conversations, and it is wrong for a method called `start`: a verb\n\t\t\t// that sometimes does not do the thing is worse than two verbs. It is\n\t\t\t// also ambiguous the moment a user has more than one conversation,\n\t\t\t// since it silently picks the newest.\n\t\t\t//\n\t\t\t// So the SDK does not rely on that default. `start` starts. To\n\t\t\t// continue a conversation, hold its id: `agent.conversation(id)`.\n\t\t\tfresh: true,\n\t\t});\n\t\treturn new Conversation(this.#http, data.id, data);\n\t}\n\n\t/**\n\t * Address one of this agent's conversations by id. No request.\n\t *\n\t * Here rather than on the workspace because a conversation belongs to an\n\t * agent. You already hold the agent: it is a constant in your own code, not\n\t * a per-row value, so naming it costs nothing and the ownership is visible.\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('carry on');\n\t * ```\n\t */\n\tconversation(conversationId: string): Conversation {\n\t\treturn new Conversation(this.#http, conversationId);\n\t}\n\n\t/**\n\t * This agent's most recent conversations, newest first.\n\t *\n\t * NOT ALL OF THEM, and the previous sentence here said \"every\", which was\n\t * wrong in the way that costs someone an afternoon. The server returns the\n\t * newest 50 unless you ask for more, and refuses to return more than 200 on\n\t * any single call (apps/api/src/state.ts listAgentSessions, and the Durable\n\t * Object twin beside it, clamp identically).\n\t *\n\t * There is no cursor on this route yet, so an agent with more than 200\n\t * conversations CANNOT be fully enumerated through this method. If you are\n\t * counting or exporting, do not build on it; that needs a paginated route we\n\t * have not added. `ws.endUser(id).conversations()` DOES have a cursor, if\n\t * the question is one person's history rather than one agent's.\n\t *\n\t * `endUser` narrows to one of YOUR end users, which is what you want for a\n\t * \"your conversations\" list. Leaving it out returns everyone's, so leave it\n\t * out only for an operator view.\n\t *\n\t * ```ts\n\t * const mine = await agent.conversations({ endUser: row.userId });\n\t * ```\n\t */\n\tasync conversations(options: { limit?: number; endUser?: string } = {}): Promise<Conversation[]> {\n\t\tconst res = await this.#http.get<{ conversations: AgentConversationData[] }>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t// One end user's history. Sending this used to be silently ignored on\n\t\t\t// this route, which answered with everyone's conversations, so an\n\t\t\t// application rendering \"your chats\" showed somebody else's.\n\t\t\t...(options.endUser !== undefined && { end_user_id: options.endUser }),\n\t\t});\n\t\treturn (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));\n\t}\n}\n\n/** A connection row as `GET /v1/connections` returns it (toConnectionMeta in\n * apps/api/src/connections/store.ts). */\n\n"]}
package/attach.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { Connection } from './connection.ts';
2
+ import { Secret } from './secret.ts';
3
+ import type { Bindings } from './types.ts';
4
+ /**
5
+ * Turn the accounts a caller is holding into the `{ provider: id }` map the
6
+ * wire speaks.
7
+ *
8
+ * A handle you created carries its provider already; one you addressed with
9
+ * `ws.connection(id)` has to read it. That read is the only request here, and
10
+ * only when it is unavoidable.
11
+ */
12
+ export declare function providersOf(what: Connection | Connection[] | undefined): Promise<Record<string, string> | undefined>;
13
+ /**
14
+ * Distinguish `attach({ secrets })` from `attach(connection)`.
15
+ *
16
+ * Structural rather than `instanceof`, so a Connection that crossed a module
17
+ * boundary or was rebuilt from JSON is still recognised. A Connection always
18
+ * has an `id`; a Bindings object has only `secrets` and `connections`.
19
+ */
20
+ export declare function isBindings(what: unknown): what is Bindings;
21
+ /**
22
+ * Turn stored secrets into the `{ DECLARED_NAME: secretId }` map the wire
23
+ * speaks.
24
+ *
25
+ * THE POINT OF SENDING AN ID RATHER THAN A VALUE. A secret stored against one
26
+ * of your end users has no value you hold: the platform encrypted it when you
27
+ * created it and never hands it back. Sending the id lets the launch resolve it
28
+ * server-side, so attaching a credential does not require your process to have
29
+ * read it.
30
+ *
31
+ * The NAME comes from the secret, because that is the name the agent declared
32
+ * and the only thing it will look under. A handle you created carries it; one
33
+ * you addressed by id has to read it, and that read is the only request here.
34
+ */
35
+ export declare function secretIdsOf(what: Secret | Secret[] | undefined): Promise<Record<string, string> | undefined>;
36
+ /** What a caller may hand to `attach`, or to `start({ attach })`. */
37
+ export type Attachable = Connection | Secret;
38
+ /**
39
+ * Split a mixed list into the accounts and the credentials.
40
+ *
41
+ * BY `instanceof`, not by duck-typing. A `Connection` addressed with
42
+ * `ws.connection(id)` has no `provider` until it reads one, and a `Secret` from
43
+ * `ws.secret(id)` has no `name`, so "has a provider" and "has a name" both
44
+ * answer wrongly for exactly the handles that need splitting most.
45
+ */
46
+ export declare function splitAttachments(what: Attachable | Attachable[] | undefined): {
47
+ connections: Connection[];
48
+ secrets: Secret[];
49
+ };
package/attach.js ADDED
@@ -0,0 +1,115 @@
1
+ // Turning the credentials a caller is holding into what the wire speaks.
2
+ //
3
+ // ITS OWN MODULE because `agent.ts` already imports `conversation.ts`, so a
4
+ // helper living in either one and used by both is a cycle. Shared so that
5
+ // `agent.start`, `agent.attach` and `conversation.attach` cannot drift: all
6
+ // three had to agree on this shape, and when they did not, the disagreement was
7
+ // invisible until a turn failed somewhere else.
8
+ // VALUE imports, not type-only, because the split below is an `instanceof`.
9
+ // Neither module imports anything but types, so this adds no cycle; `agent.ts`
10
+ // and `conversation.ts` are the ones that could not import each other, which is
11
+ // why this helper lives here in the first place.
12
+ import { Connection as ConnectionClass } from "./connection.js";
13
+ import { Secret } from "./secret.js";
14
+ /**
15
+ * Turn the accounts a caller is holding into the `{ provider: id }` map the
16
+ * wire speaks.
17
+ *
18
+ * A handle you created carries its provider already; one you addressed with
19
+ * `ws.connection(id)` has to read it. That read is the only request here, and
20
+ * only when it is unavoidable.
21
+ */
22
+ export async function providersOf(what) {
23
+ if (!what)
24
+ return undefined;
25
+ const list = Array.isArray(what) ? what : [what];
26
+ if (list.length === 0)
27
+ return undefined;
28
+ const out = {};
29
+ for (const connection of list) {
30
+ const provider = connection.provider ?? (await connection.get()).provider;
31
+ // Two different accounts for one provider in a single call is a caller
32
+ // mistake. A silent last-one-wins would surface much later as "the agent
33
+ // read the wrong mailbox", which is expensive to trace back to here.
34
+ if (out[provider] && out[provider] !== connection.id) {
35
+ throw new Error(`two different ${provider} accounts were attached in one call: ${out[provider]} and ${connection.id}`);
36
+ }
37
+ out[provider] = connection.id;
38
+ }
39
+ return out;
40
+ }
41
+ /**
42
+ * Distinguish `attach({ secrets })` from `attach(connection)`.
43
+ *
44
+ * Structural rather than `instanceof`, so a Connection that crossed a module
45
+ * boundary or was rebuilt from JSON is still recognised. A Connection always
46
+ * has an `id`; a Bindings object has only `secrets` and `connections`.
47
+ */
48
+ export function isBindings(what) {
49
+ if (Array.isArray(what) || what === null || typeof what !== 'object')
50
+ return false;
51
+ const o = what;
52
+ return !('id' in o) && ('secrets' in o || 'connections' in o);
53
+ }
54
+ /**
55
+ * Turn stored secrets into the `{ DECLARED_NAME: secretId }` map the wire
56
+ * speaks.
57
+ *
58
+ * THE POINT OF SENDING AN ID RATHER THAN A VALUE. A secret stored against one
59
+ * of your end users has no value you hold: the platform encrypted it when you
60
+ * created it and never hands it back. Sending the id lets the launch resolve it
61
+ * server-side, so attaching a credential does not require your process to have
62
+ * read it.
63
+ *
64
+ * The NAME comes from the secret, because that is the name the agent declared
65
+ * and the only thing it will look under. A handle you created carries it; one
66
+ * you addressed by id has to read it, and that read is the only request here.
67
+ */
68
+ export async function secretIdsOf(what) {
69
+ if (!what)
70
+ return undefined;
71
+ const list = Array.isArray(what) ? what : [what];
72
+ if (list.length === 0)
73
+ return undefined;
74
+ const out = {};
75
+ for (const secret of list) {
76
+ const name = secret.name ?? (await secret.get()).name;
77
+ // Two different secrets under one name in a single call is a caller
78
+ // mistake, and last-one-wins would surface much later as a tool failing
79
+ // on a credential that looks present. Same rule as two accounts for one
80
+ // provider, for the same reason.
81
+ if (out[name] && out[name] !== secret.id) {
82
+ throw new Error(`two different secrets were attached as ${name}: ${out[name]} and ${secret.id}`);
83
+ }
84
+ out[name] = secret.id;
85
+ }
86
+ return out;
87
+ }
88
+ /**
89
+ * Split a mixed list into the accounts and the credentials.
90
+ *
91
+ * BY `instanceof`, not by duck-typing. A `Connection` addressed with
92
+ * `ws.connection(id)` has no `provider` until it reads one, and a `Secret` from
93
+ * `ws.secret(id)` has no `name`, so "has a provider" and "has a name" both
94
+ * answer wrongly for exactly the handles that need splitting most.
95
+ */
96
+ export function splitAttachments(what) {
97
+ const list = what === undefined ? [] : Array.isArray(what) ? what : [what];
98
+ const connections = [];
99
+ const secrets = [];
100
+ for (const item of list) {
101
+ if (item instanceof Secret)
102
+ secrets.push(item);
103
+ else if (item instanceof ConnectionClass)
104
+ connections.push(item);
105
+ else {
106
+ // Refused rather than ignored. Silently dropping an unrecognised
107
+ // attachment is how a conversation starts without the credential its
108
+ // caller plainly attached, and the failure then appears one layer
109
+ // down as a tool that cannot authenticate.
110
+ throw new Error('attach takes a Connection or a Secret');
111
+ }
112
+ }
113
+ return { connections, secrets };
114
+ }
115
+ //# sourceMappingURL=attach.js.map
package/attach.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attach.js","sourceRoot":"","sources":["../src/attach.ts"],"names":[],"mappings":"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","sourcesContent":["// Turning the credentials a caller is holding into what the wire speaks.\n//\n// ITS OWN MODULE because `agent.ts` already imports `conversation.ts`, so a\n// helper living in either one and used by both is a cycle. Shared so that\n// `agent.start`, `agent.attach` and `conversation.attach` cannot drift: all\n// three had to agree on this shape, and when they did not, the disagreement was\n// invisible until a turn failed somewhere else.\n\nimport type { Connection } from './connection.ts';\n// VALUE imports, not type-only, because the split below is an `instanceof`.\n// Neither module imports anything but types, so this adds no cycle; `agent.ts`\n// and `conversation.ts` are the ones that could not import each other, which is\n// why this helper lives here in the first place.\nimport { Connection as ConnectionClass } from './connection.ts';\nimport { Secret } from './secret.ts';\nimport type { Bindings } from './types.ts';\n\n/**\n * Turn the accounts a caller is holding into the `{ provider: id }` map the\n * wire speaks.\n *\n * A handle you created carries its provider already; one you addressed with\n * `ws.connection(id)` has to read it. That read is the only request here, and\n * only when it is unavoidable.\n */\nexport async function providersOf(\n\twhat: Connection | Connection[] | undefined,\n): Promise<Record<string, string> | undefined> {\n\tif (!what) return undefined;\n\tconst list = Array.isArray(what) ? what : [what];\n\tif (list.length === 0) return undefined;\n\tconst out: Record<string, string> = {};\n\tfor (const connection of list) {\n\t\tconst provider = connection.provider ?? (await connection.get()).provider;\n\t\t// Two different accounts for one provider in a single call is a caller\n\t\t// mistake. A silent last-one-wins would surface much later as \"the agent\n\t\t// read the wrong mailbox\", which is expensive to trace back to here.\n\t\tif (out[provider] && out[provider] !== connection.id) {\n\t\t\tthrow new Error(\n\t\t\t\t`two different ${provider} accounts were attached in one call: ${out[provider]} and ${connection.id}`,\n\t\t\t);\n\t\t}\n\t\tout[provider] = connection.id;\n\t}\n\treturn out;\n}\n\n/**\n * Distinguish `attach({ secrets })` from `attach(connection)`.\n *\n * Structural rather than `instanceof`, so a Connection that crossed a module\n * boundary or was rebuilt from JSON is still recognised. A Connection always\n * has an `id`; a Bindings object has only `secrets` and `connections`.\n */\nexport function isBindings(what: unknown): what is Bindings {\n\tif (Array.isArray(what) || what === null || typeof what !== 'object') return false;\n\tconst o = what as Record<string, unknown>;\n\treturn !('id' in o) && ('secrets' in o || 'connections' in o);\n}\n\n/**\n * Turn stored secrets into the `{ DECLARED_NAME: secretId }` map the wire\n * speaks.\n *\n * THE POINT OF SENDING AN ID RATHER THAN A VALUE. A secret stored against one\n * of your end users has no value you hold: the platform encrypted it when you\n * created it and never hands it back. Sending the id lets the launch resolve it\n * server-side, so attaching a credential does not require your process to have\n * read it.\n *\n * The NAME comes from the secret, because that is the name the agent declared\n * and the only thing it will look under. A handle you created carries it; one\n * you addressed by id has to read it, and that read is the only request here.\n */\nexport async function secretIdsOf(\n\twhat: Secret | Secret[] | undefined,\n): Promise<Record<string, string> | undefined> {\n\tif (!what) return undefined;\n\tconst list = Array.isArray(what) ? what : [what];\n\tif (list.length === 0) return undefined;\n\tconst out: Record<string, string> = {};\n\tfor (const secret of list) {\n\t\tconst name = secret.name ?? (await secret.get()).name;\n\t\t// Two different secrets under one name in a single call is a caller\n\t\t// mistake, and last-one-wins would surface much later as a tool failing\n\t\t// on a credential that looks present. Same rule as two accounts for one\n\t\t// provider, for the same reason.\n\t\tif (out[name] && out[name] !== secret.id) {\n\t\t\tthrow new Error(\n\t\t\t\t`two different secrets were attached as ${name}: ${out[name]} and ${secret.id}`,\n\t\t\t);\n\t\t}\n\t\tout[name] = secret.id;\n\t}\n\treturn out;\n}\n\n/** What a caller may hand to `attach`, or to `start({ attach })`. */\nexport type Attachable = Connection | Secret;\n\n/**\n * Split a mixed list into the accounts and the credentials.\n *\n * BY `instanceof`, not by duck-typing. A `Connection` addressed with\n * `ws.connection(id)` has no `provider` until it reads one, and a `Secret` from\n * `ws.secret(id)` has no `name`, so \"has a provider\" and \"has a name\" both\n * answer wrongly for exactly the handles that need splitting most.\n */\nexport function splitAttachments(\n\twhat: Attachable | Attachable[] | undefined,\n): { connections: Connection[]; secrets: Secret[] } {\n\tconst list = what === undefined ? [] : Array.isArray(what) ? what : [what];\n\tconst connections: Connection[] = [];\n\tconst secrets: Secret[] = [];\n\tfor (const item of list) {\n\t\tif (item instanceof Secret) secrets.push(item);\n\t\telse if (item instanceof ConnectionClass) connections.push(item);\n\t\telse {\n\t\t\t// Refused rather than ignored. Silently dropping an unrecognised\n\t\t\t// attachment is how a conversation starts without the credential its\n\t\t\t// caller plainly attached, and the failure then appears one layer\n\t\t\t// down as a tool that cannot authenticate.\n\t\t\tthrow new Error('attach takes a Connection or a Secret');\n\t\t}\n\t}\n\treturn { connections, secrets };\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import type { ConnectionStatus } from './types.ts';
2
+ /** A connection row exactly as `/v1/connections` sends it. */
3
+ export interface WireConnection {
4
+ id: string;
5
+ provider: string;
6
+ agentId: string | null;
7
+ conversationId: string | null;
8
+ /** Present on end-user rows. Absent on older servers, hence optional. */
9
+ endUserId?: string | null;
10
+ accountLabel: string | null;
11
+ status: string;
12
+ lastError?: string | null;
13
+ }
14
+ /**
15
+ * A wire row as the SDK's ConnectionStatus.
16
+ *
17
+ * The API reports `status` as a string and the SDK reports `connected` as a
18
+ * boolean, because `if (c.connected)` is the question a call site actually asks,
19
+ * and comparing against a magic string is the thing everyone gets subtly wrong.
20
+ * `status` is carried through for anyone who needs the detail.
21
+ *
22
+ * `scope` and `suppliedBy` are DERIVED from which id the row carries, the same
23
+ * rule the API applies when it decides which row to write (ADR 0017):
24
+ * a conversation row belongs to one thread, an end-user row belongs to a person
25
+ * and outlives their threads, and a row with neither is the agent's shared
26
+ * account.
27
+ */
28
+ export declare function toConnectionStatus(row: WireConnection): ConnectionStatus;
@@ -0,0 +1,61 @@
1
+ // Turning a wire row into a ConnectionStatus, in ONE place.
2
+ //
3
+ // This function already existed, privately, inside agent.ts. Two other call
4
+ // sites listed connections and did not use it: `ws.endUser(id).connections
5
+ // .list()` and `ws.connections.list()` both handed the raw API row straight to
6
+ // `new Connection(...)`.
7
+ //
8
+ // That mattered because the wire row and the SDK type are NOT the same shape.
9
+ // The API sends `status: 'connected'` and no `connected` boolean at all, so
10
+ // every end-user and workspace account came back with `connected === undefined`.
11
+ // `ConnectionStatus` declares `connected` as a required boolean, so
12
+ // `if (c.connected)` type-checks perfectly and is false at runtime, forever.
13
+ // Filtering on it silently discarded accounts that both Composio and our own API
14
+ // called connected, and the application waited for something that had already
15
+ // happened.
16
+ //
17
+ // `declared`, `required` and `error` were absent the same way, and `error` is
18
+ // additionally a RENAME: the wire calls it `lastError`.
19
+ /**
20
+ * A wire row as the SDK's ConnectionStatus.
21
+ *
22
+ * The API reports `status` as a string and the SDK reports `connected` as a
23
+ * boolean, because `if (c.connected)` is the question a call site actually asks,
24
+ * and comparing against a magic string is the thing everyone gets subtly wrong.
25
+ * `status` is carried through for anyone who needs the detail.
26
+ *
27
+ * `scope` and `suppliedBy` are DERIVED from which id the row carries, the same
28
+ * rule the API applies when it decides which row to write (ADR 0017):
29
+ * a conversation row belongs to one thread, an end-user row belongs to a person
30
+ * and outlives their threads, and a row with neither is the agent's shared
31
+ * account.
32
+ */
33
+ export function toConnectionStatus(row) {
34
+ const owned = row.conversationId
35
+ ? 'conversation'
36
+ : row.endUserId
37
+ ? 'endUser'
38
+ : 'agent';
39
+ return {
40
+ id: row.id,
41
+ provider: row.provider,
42
+ status: row.status,
43
+ connected: row.status === 'connected',
44
+ suppliedBy: owned,
45
+ scope: owned === 'conversation' ? 'conversation' : 'fixed',
46
+ // `declared` and `required` are DELIBERATELY ABSENT.
47
+ //
48
+ // They are properties of the agent's configuration, and this endpoint
49
+ // lists rows without reference to any agent, so it cannot know either. It
50
+ // used to fill them with `true` and `false`, which was worse than leaving
51
+ // them out: every row claimed to be declared, no row claimed to be
52
+ // required, and both looked like answers. `agent.connections()` reads the
53
+ // merged view and has the real values.
54
+ ...(row.agentId != null && { agentId: row.agentId }),
55
+ ...(row.conversationId != null && { conversationId: row.conversationId }),
56
+ ...(row.endUserId != null && { endUserId: row.endUserId }),
57
+ ...(row.accountLabel != null && { accountLabel: row.accountLabel }),
58
+ ...(row.lastError != null && { error: row.lastError }),
59
+ };
60
+ }
61
+ //# sourceMappingURL=connection-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-status.js","sourceRoot":"","sources":["../src/connection-status.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,+EAA+E;AAC/E,yBAAyB;AACzB,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,iFAAiF;AACjF,oEAAoE;AACpE,6EAA6E;AAC7E,iFAAiF;AACjF,8EAA8E;AAC9E,YAAY;AACZ,EAAE;AACF,8EAA8E;AAC9E,wDAAwD;AAiBxD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAmB;IACrD,MAAM,KAAK,GAAG,GAAG,CAAC,cAAc;QAC/B,CAAC,CAAE,cAAwB;QAC3B,CAAC,CAAC,GAAG,CAAC,SAAS;YACd,CAAC,CAAE,SAAmB;YACtB,CAAC,CAAE,OAAiB,CAAC;IACvB,OAAO;QACN,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,SAAS,EAAE,GAAG,CAAC,MAAM,KAAK,WAAW;QACrC,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,KAAK,KAAK,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO;QAC1D,qDAAqD;QACrD,EAAE;QACF,sEAAsE;QACtE,0EAA0E;QAC1E,0EAA0E;QAC1E,mEAAmE;QACnE,0EAA0E;QAC1E,uCAAuC;QACvC,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;QACpD,GAAG,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,IAAI,EAAE,cAAc,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC;QACzE,GAAG,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;QAC1D,GAAG,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;QACnE,GAAG,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;KACtD,CAAC;AACH,CAAC","sourcesContent":["// Turning a wire row into a ConnectionStatus, in ONE place.\n//\n// This function already existed, privately, inside agent.ts. Two other call\n// sites listed connections and did not use it: `ws.endUser(id).connections\n// .list()` and `ws.connections.list()` both handed the raw API row straight to\n// `new Connection(...)`.\n//\n// That mattered because the wire row and the SDK type are NOT the same shape.\n// The API sends `status: 'connected'` and no `connected` boolean at all, so\n// every end-user and workspace account came back with `connected === undefined`.\n// `ConnectionStatus` declares `connected` as a required boolean, so\n// `if (c.connected)` type-checks perfectly and is false at runtime, forever.\n// Filtering on it silently discarded accounts that both Composio and our own API\n// called connected, and the application waited for something that had already\n// happened.\n//\n// `declared`, `required` and `error` were absent the same way, and `error` is\n// additionally a RENAME: the wire calls it `lastError`.\n\nimport type { ConnectionStatus } from './types.ts';\n\n/** A connection row exactly as `/v1/connections` sends it. */\nexport interface WireConnection {\n\tid: string;\n\tprovider: string;\n\tagentId: string | null;\n\tconversationId: string | null;\n\t/** Present on end-user rows. Absent on older servers, hence optional. */\n\tendUserId?: string | null;\n\taccountLabel: string | null;\n\tstatus: string;\n\tlastError?: string | null;\n}\n\n/**\n * A wire row as the SDK's ConnectionStatus.\n *\n * The API reports `status` as a string and the SDK reports `connected` as a\n * boolean, because `if (c.connected)` is the question a call site actually asks,\n * and comparing against a magic string is the thing everyone gets subtly wrong.\n * `status` is carried through for anyone who needs the detail.\n *\n * `scope` and `suppliedBy` are DERIVED from which id the row carries, the same\n * rule the API applies when it decides which row to write (ADR 0017):\n * a conversation row belongs to one thread, an end-user row belongs to a person\n * and outlives their threads, and a row with neither is the agent's shared\n * account.\n */\nexport function toConnectionStatus(row: WireConnection): ConnectionStatus {\n\tconst owned = row.conversationId\n\t\t? ('conversation' as const)\n\t\t: row.endUserId\n\t\t\t? ('endUser' as const)\n\t\t\t: ('agent' as const);\n\treturn {\n\t\tid: row.id,\n\t\tprovider: row.provider,\n\t\tstatus: row.status,\n\t\tconnected: row.status === 'connected',\n\t\tsuppliedBy: owned,\n\t\tscope: owned === 'conversation' ? 'conversation' : 'fixed',\n\t\t// `declared` and `required` are DELIBERATELY ABSENT.\n\t\t//\n\t\t// They are properties of the agent's configuration, and this endpoint\n\t\t// lists rows without reference to any agent, so it cannot know either. It\n\t\t// used to fill them with `true` and `false`, which was worse than leaving\n\t\t// them out: every row claimed to be declared, no row claimed to be\n\t\t// required, and both looked like answers. `agent.connections()` reads the\n\t\t// merged view and has the real values.\n\t\t...(row.agentId != null && { agentId: row.agentId }),\n\t\t...(row.conversationId != null && { conversationId: row.conversationId }),\n\t\t...(row.endUserId != null && { endUserId: row.endUserId }),\n\t\t...(row.accountLabel != null && { accountLabel: row.accountLabel }),\n\t\t...(row.lastError != null && { error: row.lastError }),\n\t};\n}\n"]}
@@ -0,0 +1,89 @@
1
+ import type { Http } from './http.ts';
2
+ import type { CredentialOwner, ConnectionStatus } from './types.ts';
3
+ /**
4
+ * An account somebody attached, that you hold and act on.
5
+ *
6
+ * A CLASS, like `Agent`, `Conversation`, `Project` and `KnowledgeBase`. It was
7
+ * briefly a bare `ConnectionStatus` with an id you carried around as a string,
8
+ * which made it the only thing in this SDK you could not hold. Holding it is
9
+ * the point: you create one, wait for the person to finish signing in, attach
10
+ * it where it is needed, and revoke it when they leave, and each of those is a
11
+ * method on the thing rather than a call with an id threaded through it.
12
+ *
13
+ * ```ts
14
+ * const gmail = await ws.connections.create({ provider: 'gmail', endUserId: 'u_ananya' });
15
+ * showToUser(gmail.url);
16
+ * await gmail.waitUntilConnected();
17
+ * await agent.attach(gmail);
18
+ * ```
19
+ *
20
+ * Holding one makes no request. `ws.connection(id)` is free and the first call
21
+ * on it is the first round trip.
22
+ */
23
+ export declare class Connection {
24
+ #private;
25
+ readonly id: string;
26
+ /** Present when the person has not finished signing in yet: the url to send
27
+ * them to. Absent on a connection you looked up rather than created. */
28
+ readonly url?: string;
29
+ private snapshot?;
30
+ constructor(http: Http, id: string, snapshot?: ConnectionStatus, url?: string);
31
+ /** Which third party this is: `'gmail'`, `'hubspot'`. Named for what the
32
+ * person connecting recognises, never for whoever we buy it from. */
33
+ get provider(): string | undefined;
34
+ /** True once the account is attached and usable.
35
+ *
36
+ * Reads the last snapshot rather than the network, the same rule
37
+ * `conversation.ready` follows. Undefined means this handle has not read
38
+ * yet, NOT that it is disconnected. */
39
+ get connected(): boolean | undefined;
40
+ /** The raw status string, for the cases where the boolean is too coarse:
41
+ * 'pending' and 'error' are both "not connected" and mean opposite things.
42
+ * This used to be reachable only through `.data`, the whole-snapshot escape
43
+ * hatch, which is a poor thing to make a caller reach for. */
44
+ get status(): string | undefined;
45
+ /**
46
+ * Whose account it is: the workspace's, one agent's, one of YOUR END USERS',
47
+ * or one conversation's.
48
+ *
49
+ * DERIVED HERE, from which id the row carries, rather than read off a field.
50
+ * This getter used to return `snapshot.scope`, which is a different question
51
+ * entirely: `scope` is what an agent DECLARED it needs ('fixed' or
52
+ * 'conversation'), and the server never sent an ownership field at all. So
53
+ * it answered `undefined` for every connection you looked up, and for one
54
+ * you had just created it answered `'user'`, a value outside its own
55
+ * declared type that only compiled through an `as never` cast.
56
+ *
57
+ * Most specific first: a conversation's own account, then an end user's,
58
+ * then an agent's, then the workspace's.
59
+ */
60
+ get owner(): CredentialOwner | undefined;
61
+ /** One of YOUR end users, when this account is theirs. Never a workspace
62
+ * member: the two are separate id namespaces. */
63
+ get endUserId(): string | null | undefined;
64
+ /** The last-known state, from whatever call produced this object. */
65
+ get data(): ConnectionStatus | undefined;
66
+ /** Read it back. */
67
+ get(): Promise<ConnectionStatus>;
68
+ /**
69
+ * Wait until the person has finished signing in.
70
+ *
71
+ * The companion to `create()`. That hands you a url; this tells you when
72
+ * they are done, so you do not write a polling loop or guess which field to
73
+ * believe.
74
+ *
75
+ * Polls rather than subscribing: there is a human in the middle, so a few
76
+ * seconds costs nothing and it works from any runtime without a socket.
77
+ */
78
+ waitUntilConnected(options?: {
79
+ timeoutMs?: number;
80
+ intervalMs?: number;
81
+ }): Promise<Connection>;
82
+ /**
83
+ * Detach the account and drop its tokens.
84
+ *
85
+ * This is what off-boarding calls. Every conversation using it stops acting
86
+ * as that person from the next turn; nothing is retained.
87
+ */
88
+ revoke(): Promise<void>;
89
+ }
package/connection.js ADDED
@@ -0,0 +1,143 @@
1
+ // A connection: a third-party account an agent acts through.
2
+ /**
3
+ * An account somebody attached, that you hold and act on.
4
+ *
5
+ * A CLASS, like `Agent`, `Conversation`, `Project` and `KnowledgeBase`. It was
6
+ * briefly a bare `ConnectionStatus` with an id you carried around as a string,
7
+ * which made it the only thing in this SDK you could not hold. Holding it is
8
+ * the point: you create one, wait for the person to finish signing in, attach
9
+ * it where it is needed, and revoke it when they leave, and each of those is a
10
+ * method on the thing rather than a call with an id threaded through it.
11
+ *
12
+ * ```ts
13
+ * const gmail = await ws.connections.create({ provider: 'gmail', endUserId: 'u_ananya' });
14
+ * showToUser(gmail.url);
15
+ * await gmail.waitUntilConnected();
16
+ * await agent.attach(gmail);
17
+ * ```
18
+ *
19
+ * Holding one makes no request. `ws.connection(id)` is free and the first call
20
+ * on it is the first round trip.
21
+ */
22
+ export class Connection {
23
+ id;
24
+ #http;
25
+ /** Present when the person has not finished signing in yet: the url to send
26
+ * them to. Absent on a connection you looked up rather than created. */
27
+ url;
28
+ snapshot;
29
+ constructor(http, id, snapshot, url) {
30
+ this.#http = http;
31
+ this.id = id;
32
+ if (snapshot)
33
+ this.snapshot = snapshot;
34
+ if (url)
35
+ this.url = url;
36
+ }
37
+ /** Which third party this is: `'gmail'`, `'hubspot'`. Named for what the
38
+ * person connecting recognises, never for whoever we buy it from. */
39
+ get provider() {
40
+ return this.snapshot?.provider;
41
+ }
42
+ /** True once the account is attached and usable.
43
+ *
44
+ * Reads the last snapshot rather than the network, the same rule
45
+ * `conversation.ready` follows. Undefined means this handle has not read
46
+ * yet, NOT that it is disconnected. */
47
+ get connected() {
48
+ const s = this.snapshot;
49
+ if (!s)
50
+ return undefined;
51
+ // Belt as well as braces. Every list path now normalises before
52
+ // constructing a Connection, but this object is also built directly from
53
+ // server payloads elsewhere, and a pass-through here meant a row that
54
+ // plainly said `status: 'connected'` reported `connected === undefined`.
55
+ return s.connected ?? s.status === 'connected';
56
+ }
57
+ /** The raw status string, for the cases where the boolean is too coarse:
58
+ * 'pending' and 'error' are both "not connected" and mean opposite things.
59
+ * This used to be reachable only through `.data`, the whole-snapshot escape
60
+ * hatch, which is a poor thing to make a caller reach for. */
61
+ get status() {
62
+ return this.snapshot?.status;
63
+ }
64
+ /**
65
+ * Whose account it is: the workspace's, one agent's, one of YOUR END USERS',
66
+ * or one conversation's.
67
+ *
68
+ * DERIVED HERE, from which id the row carries, rather than read off a field.
69
+ * This getter used to return `snapshot.scope`, which is a different question
70
+ * entirely: `scope` is what an agent DECLARED it needs ('fixed' or
71
+ * 'conversation'), and the server never sent an ownership field at all. So
72
+ * it answered `undefined` for every connection you looked up, and for one
73
+ * you had just created it answered `'user'`, a value outside its own
74
+ * declared type that only compiled through an `as never` cast.
75
+ *
76
+ * Most specific first: a conversation's own account, then an end user's,
77
+ * then an agent's, then the workspace's.
78
+ */
79
+ get owner() {
80
+ const s = this.snapshot;
81
+ if (!s)
82
+ return undefined;
83
+ if (s.conversationId)
84
+ return 'conversation';
85
+ if (s.endUserId)
86
+ return 'endUser';
87
+ if (s.agentId)
88
+ return 'agent';
89
+ return 'workspace';
90
+ }
91
+ /** One of YOUR end users, when this account is theirs. Never a workspace
92
+ * member: the two are separate id namespaces. */
93
+ get endUserId() {
94
+ return this.snapshot?.endUserId;
95
+ }
96
+ /** The last-known state, from whatever call produced this object. */
97
+ get data() {
98
+ return this.snapshot;
99
+ }
100
+ /** Read it back. */
101
+ async get() {
102
+ const res = await this.#http.get('/v1/connections');
103
+ const mine = (res.connections ?? []).find((c) => c.id === this.id);
104
+ if (mine)
105
+ this.snapshot = mine;
106
+ return mine ?? { id: this.id, provider: '', declared: false, required: false, connected: false };
107
+ }
108
+ /**
109
+ * Wait until the person has finished signing in.
110
+ *
111
+ * The companion to `create()`. That hands you a url; this tells you when
112
+ * they are done, so you do not write a polling loop or guess which field to
113
+ * believe.
114
+ *
115
+ * Polls rather than subscribing: there is a human in the middle, so a few
116
+ * seconds costs nothing and it works from any runtime without a socket.
117
+ */
118
+ async waitUntilConnected(options = {}) {
119
+ const timeoutMs = options.timeoutMs ?? 120_000;
120
+ const intervalMs = options.intervalMs ?? 2_000;
121
+ const deadline = Date.now() + timeoutMs;
122
+ // Checked before the first sleep: an already-connected account should
123
+ // cost one request, not one request plus an interval.
124
+ let state = await this.get();
125
+ while (!state.connected) {
126
+ if (Date.now() >= deadline)
127
+ return this;
128
+ await new Promise((r) => setTimeout(r, Math.min(intervalMs, Math.max(0, deadline - Date.now()))));
129
+ state = await this.get();
130
+ }
131
+ return this;
132
+ }
133
+ /**
134
+ * Detach the account and drop its tokens.
135
+ *
136
+ * This is what off-boarding calls. Every conversation using it stops acting
137
+ * as that person from the next turn; nothing is retained.
138
+ */
139
+ async revoke() {
140
+ await this.#http.post(`/v1/connections/${encodeURIComponent(this.id)}/revoke`);
141
+ }
142
+ }
143
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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","sourcesContent":["// A connection: a third-party account an agent acts through.\n\nimport type { Http } from './http.ts';\nimport type { CredentialOwner, ConnectionStatus } from './types.ts';\n\n/**\n * An account somebody attached, that you hold and act on.\n *\n * A CLASS, like `Agent`, `Conversation`, `Project` and `KnowledgeBase`. It was\n * briefly a bare `ConnectionStatus` with an id you carried around as a string,\n * which made it the only thing in this SDK you could not hold. Holding it is\n * the point: you create one, wait for the person to finish signing in, attach\n * it where it is needed, and revoke it when they leave, and each of those is a\n * method on the thing rather than a call with an id threaded through it.\n *\n * ```ts\n * const gmail = await ws.connections.create({ provider: 'gmail', endUserId: 'u_ananya' });\n * showToUser(gmail.url);\n * await gmail.waitUntilConnected();\n * await agent.attach(gmail);\n * ```\n *\n * Holding one makes no request. `ws.connection(id)` is free and the first call\n * on it is the first round trip.\n */\nexport class Connection {\n\treadonly id: string;\n\treadonly #http: Http;\n\t/** Present when the person has not finished signing in yet: the url to send\n\t * them to. Absent on a connection you looked up rather than created. */\n\treadonly url?: string;\n\tprivate snapshot?: ConnectionStatus;\n\n\tconstructor(http: Http, id: string, snapshot?: ConnectionStatus, url?: string) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t\tif (url) this.url = url;\n\t}\n\n\t/** Which third party this is: `'gmail'`, `'hubspot'`. Named for what the\n\t * person connecting recognises, never for whoever we buy it from. */\n\tget provider(): string | undefined {\n\t\treturn this.snapshot?.provider;\n\t}\n\n\t/** True once the account is attached and usable.\n\t *\n\t * Reads the last snapshot rather than the network, the same rule\n\t * `conversation.ready` follows. Undefined means this handle has not read\n\t * yet, NOT that it is disconnected. */\n\tget connected(): boolean | undefined {\n\t\tconst s = this.snapshot;\n\t\tif (!s) return undefined;\n\t\t// Belt as well as braces. Every list path now normalises before\n\t\t// constructing a Connection, but this object is also built directly from\n\t\t// server payloads elsewhere, and a pass-through here meant a row that\n\t\t// plainly said `status: 'connected'` reported `connected === undefined`.\n\t\treturn s.connected ?? s.status === 'connected';\n\t}\n\n\t/** The raw status string, for the cases where the boolean is too coarse:\n\t * 'pending' and 'error' are both \"not connected\" and mean opposite things.\n\t * This used to be reachable only through `.data`, the whole-snapshot escape\n\t * hatch, which is a poor thing to make a caller reach for. */\n\tget status(): string | undefined {\n\t\treturn this.snapshot?.status;\n\t}\n\n\t/**\n\t * Whose account it is: the workspace's, one agent's, one of YOUR END USERS',\n\t * or one conversation's.\n\t *\n\t * DERIVED HERE, from which id the row carries, rather than read off a field.\n\t * This getter used to return `snapshot.scope`, which is a different question\n\t * entirely: `scope` is what an agent DECLARED it needs ('fixed' or\n\t * 'conversation'), and the server never sent an ownership field at all. So\n\t * it answered `undefined` for every connection you looked up, and for one\n\t * you had just created it answered `'user'`, a value outside its own\n\t * declared type that only compiled through an `as never` cast.\n\t *\n\t * Most specific first: a conversation's own account, then an end user's,\n\t * then an agent's, then the workspace's.\n\t */\n\tget owner(): CredentialOwner | undefined {\n\t\tconst s = this.snapshot;\n\t\tif (!s) return undefined;\n\t\tif (s.conversationId) return 'conversation';\n\t\tif (s.endUserId) return 'endUser';\n\t\tif (s.agentId) return 'agent';\n\t\treturn 'workspace';\n\t}\n\n\t/** One of YOUR end users, when this account is theirs. Never a workspace\n\t * member: the two are separate id namespaces. */\n\tget endUserId(): string | null | undefined {\n\t\treturn this.snapshot?.endUserId;\n\t}\n\n\t/** The last-known state, from whatever call produced this object. */\n\tget data(): ConnectionStatus | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\t/** Read it back. */\n\tasync get(): Promise<ConnectionStatus> {\n\t\tconst res = await this.#http.get<{ connections: ConnectionStatus[] }>('/v1/connections');\n\t\tconst mine = (res.connections ?? []).find((c) => c.id === this.id);\n\t\tif (mine) this.snapshot = mine;\n\t\treturn mine ?? ({ id: this.id, provider: '', declared: false, required: false, connected: false } as ConnectionStatus);\n\t}\n\n\t/**\n\t * Wait until the person has finished signing in.\n\t *\n\t * The companion to `create()`. That hands you a url; this tells you when\n\t * they are done, so you do not write a polling loop or guess which field to\n\t * believe.\n\t *\n\t * Polls rather than subscribing: there is a human in the middle, so a few\n\t * seconds costs nothing and it works from any runtime without a socket.\n\t */\n\tasync waitUntilConnected(options: { timeoutMs?: number; intervalMs?: number } = {}): Promise<Connection> {\n\t\tconst timeoutMs = options.timeoutMs ?? 120_000;\n\t\tconst intervalMs = options.intervalMs ?? 2_000;\n\t\tconst deadline = Date.now() + timeoutMs;\n\t\t// Checked before the first sleep: an already-connected account should\n\t\t// cost one request, not one request plus an interval.\n\t\tlet state = await this.get();\n\t\twhile (!state.connected) {\n\t\t\tif (Date.now() >= deadline) return this;\n\t\t\tawait new Promise((r) => setTimeout(r, Math.min(intervalMs, Math.max(0, deadline - Date.now()))));\n\t\t\tstate = await this.get();\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Detach the account and drop its tokens.\n\t *\n\t * This is what off-boarding calls. Every conversation using it stops acting\n\t * as that person from the next turn; nothing is retained.\n\t */\n\tasync revoke(): Promise<void> {\n\t\tawait this.#http.post(`/v1/connections/${encodeURIComponent(this.id)}/revoke`);\n\t}\n}\n"]}