@theokit/sdk 5.13.0 → 5.14.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 (107) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{agent-iW5kPpfT.d.cts → agent-DExqjqYy.d.cts} +9 -1
  3. package/dist/{agent-DSMuwADp.d.ts → agent-Dfw3VsIX.d.ts} +9 -1
  4. package/dist/{agent-5DSDDH7K.js → agent-T34LCGQM.js} +11 -11
  5. package/dist/{agent-5DSDDH7K.js.map → agent-T34LCGQM.js.map} +1 -1
  6. package/dist/{agent-WOMRT3KX.cjs → agent-TL4K7BTG.cjs} +12 -12
  7. package/dist/{agent-WOMRT3KX.cjs.map → agent-TL4K7BTG.cjs.map} +1 -1
  8. package/dist/auth/index.cjs +17 -16
  9. package/dist/auth/index.cjs.map +1 -1
  10. package/dist/auth/index.js +5 -4
  11. package/dist/auth/index.js.map +1 -1
  12. package/dist/{chunk-NHOEXFZT.js → chunk-2HXTNVLM.js} +5 -2
  13. package/dist/chunk-2HXTNVLM.js.map +1 -0
  14. package/dist/{chunk-KL7ZUMK3.cjs → chunk-2WY2OFSC.cjs} +146 -77
  15. package/dist/chunk-2WY2OFSC.cjs.map +1 -0
  16. package/dist/{chunk-3FSBE4PS.js → chunk-3GTN4HOO.js} +10 -6
  17. package/dist/chunk-3GTN4HOO.js.map +1 -0
  18. package/dist/{chunk-XOYNB3YN.js → chunk-3ONIVO2I.js} +3 -3
  19. package/dist/{chunk-XOYNB3YN.js.map → chunk-3ONIVO2I.js.map} +1 -1
  20. package/dist/{chunk-7WARNHWW.cjs → chunk-3UMPP553.cjs} +5 -2
  21. package/dist/chunk-3UMPP553.cjs.map +1 -0
  22. package/dist/{chunk-NWWTYBD3.cjs → chunk-4KBI37F3.cjs} +5 -5
  23. package/dist/{chunk-NWWTYBD3.cjs.map → chunk-4KBI37F3.cjs.map} +1 -1
  24. package/dist/{chunk-S46WJTDL.cjs → chunk-4TEGFMSP.cjs} +6 -6
  25. package/dist/{chunk-S46WJTDL.cjs.map → chunk-4TEGFMSP.cjs.map} +1 -1
  26. package/dist/{chunk-QISSETTG.cjs → chunk-5645PMVU.cjs} +6 -4
  27. package/dist/chunk-5645PMVU.cjs.map +1 -0
  28. package/dist/{chunk-5A4UFBHW.cjs → chunk-A77UMVMD.cjs} +15 -11
  29. package/dist/chunk-A77UMVMD.cjs.map +1 -0
  30. package/dist/{chunk-47EACDOC.cjs → chunk-BZK5QL3N.cjs} +4 -4
  31. package/dist/{chunk-47EACDOC.cjs.map → chunk-BZK5QL3N.cjs.map} +1 -1
  32. package/dist/{chunk-5HSMH2ZZ.js → chunk-CJN5CEI3.js} +4 -4
  33. package/dist/{chunk-5HSMH2ZZ.js.map → chunk-CJN5CEI3.js.map} +1 -1
  34. package/dist/{chunk-JX4LB6XO.js → chunk-GARVAUCU.js} +93 -24
  35. package/dist/chunk-GARVAUCU.js.map +1 -0
  36. package/dist/{chunk-DVP3YMDA.cjs → chunk-LXG6VL6R.cjs} +3 -3
  37. package/dist/{chunk-DVP3YMDA.cjs.map → chunk-LXG6VL6R.cjs.map} +1 -1
  38. package/dist/{chunk-ZTP6ZACR.js → chunk-NAVUTK6X.js} +3 -3
  39. package/dist/{chunk-ZTP6ZACR.js.map → chunk-NAVUTK6X.js.map} +1 -1
  40. package/dist/{chunk-2SFLLOMN.js → chunk-NVQSBRG6.js} +3 -3
  41. package/dist/{chunk-2SFLLOMN.js.map → chunk-NVQSBRG6.js.map} +1 -1
  42. package/dist/{chunk-MS3TQSXU.js → chunk-QD6B6W6I.js} +6 -4
  43. package/dist/chunk-QD6B6W6I.js.map +1 -0
  44. package/dist/chunk-R5UQL2R5.cjs +18 -0
  45. package/dist/{chunk-LO6LIUZ6.cjs.map → chunk-R5UQL2R5.cjs.map} +1 -1
  46. package/dist/{chunk-4UYHP6NF.cjs → chunk-SNVYOX46.cjs} +28 -28
  47. package/dist/{chunk-4UYHP6NF.cjs.map → chunk-SNVYOX46.cjs.map} +1 -1
  48. package/dist/{chunk-NKQVLWDF.js → chunk-ULEK4OM4.js} +3 -3
  49. package/dist/{chunk-NKQVLWDF.js.map → chunk-ULEK4OM4.js.map} +1 -1
  50. package/dist/{chunk-2PRGRYEQ.js → chunk-ZOL3L4EH.js} +4 -4
  51. package/dist/{chunk-2PRGRYEQ.js.map → chunk-ZOL3L4EH.js.map} +1 -1
  52. package/dist/{compact-session-B7P6EU3Z.cjs → compact-session-D2MQDB6G.cjs} +15 -15
  53. package/dist/{compact-session-B7P6EU3Z.cjs.map → compact-session-D2MQDB6G.cjs.map} +1 -1
  54. package/dist/{compact-session-5HMX6KYT.js → compact-session-N2PPHQO6.js} +6 -6
  55. package/dist/{compact-session-5HMX6KYT.js.map → compact-session-N2PPHQO6.js.map} +1 -1
  56. package/dist/{cron-DdKzwJqK.d.ts → cron-BubDpFJG.d.ts} +2 -2
  57. package/dist/{cron-BvxrLW5J.d.cts → cron-CK2v0VWA.d.cts} +2 -2
  58. package/dist/cron.cjs +11 -11
  59. package/dist/cron.d.cts +3 -3
  60. package/dist/cron.d.ts +3 -3
  61. package/dist/cron.js +10 -10
  62. package/dist/eval.cjs +10 -10
  63. package/dist/eval.js +9 -9
  64. package/dist/index.cjs +69 -53
  65. package/dist/index.cjs.map +1 -1
  66. package/dist/index.d.cts +9 -6
  67. package/dist/index.d.ts +9 -6
  68. package/dist/index.js +38 -22
  69. package/dist/index.js.map +1 -1
  70. package/dist/{inject-session-WOH7TPDX.cjs → inject-session-3ICZIJ2N.cjs} +4 -4
  71. package/dist/{inject-session-WOH7TPDX.cjs.map → inject-session-3ICZIJ2N.cjs.map} +1 -1
  72. package/dist/{inject-session-75L3NCNL.js → inject-session-J26SHHIO.js} +3 -3
  73. package/dist/{inject-session-75L3NCNL.js.map → inject-session-J26SHHIO.js.map} +1 -1
  74. package/dist/internal/agent-loop/prune-tool-results.d.ts +37 -0
  75. package/dist/internal/auth/credential-store.d.ts +1 -1
  76. package/dist/internal/auth/types.d.ts +4 -0
  77. package/dist/internal/runtime/skills/binary-on-path.d.ts +1 -0
  78. package/dist/internal/runtime/skills/discover-skills.d.ts +2 -0
  79. package/dist/mcp-auth.cjs +2 -2
  80. package/dist/mcp-auth.js +1 -1
  81. package/dist/models.cjs +11 -11
  82. package/dist/models.js +4 -4
  83. package/dist/providers.cjs +6 -6
  84. package/dist/providers.js +4 -4
  85. package/dist/{sdk-agent-4-dXI2GS.d.cts → sdk-agent-DwzHcuxy.d.cts} +28 -3
  86. package/dist/{sdk-agent-CTOJlWA_.d.ts → sdk-agent-a3fW1xUK.d.ts} +28 -3
  87. package/dist/skills.cjs +4 -4
  88. package/dist/skills.js +1 -1
  89. package/dist/subagents-loader.d.cts +2 -2
  90. package/dist/subagents-loader.d.ts +2 -2
  91. package/dist/types/agent.d.ts +6 -0
  92. package/dist/types/run.d.ts +27 -2
  93. package/dist/workflow.d.cts +1 -1
  94. package/dist/workflow.d.ts +1 -1
  95. package/docs/adr/mutation-testing-scope.md +56 -0
  96. package/docs/error-codes.md +8 -6
  97. package/docs/harness-capability-map.md +29 -1
  98. package/package.json +1 -1
  99. package/dist/chunk-3FSBE4PS.js.map +0 -1
  100. package/dist/chunk-5A4UFBHW.cjs.map +0 -1
  101. package/dist/chunk-7WARNHWW.cjs.map +0 -1
  102. package/dist/chunk-JX4LB6XO.js.map +0 -1
  103. package/dist/chunk-KL7ZUMK3.cjs.map +0 -1
  104. package/dist/chunk-LO6LIUZ6.cjs +0 -18
  105. package/dist/chunk-MS3TQSXU.js.map +0 -1
  106. package/dist/chunk-NHOEXFZT.js.map +0 -1
  107. package/dist/chunk-QISSETTG.cjs.map +0 -1
@@ -0,0 +1,56 @@
1
+ # Mutation testing covers only the B-026 spike over `src/internal/llm`
2
+
3
+ - Status: accepted
4
+ - Date: 2026-10-17
5
+ - Decided by: human/paulo
6
+ - Soft cap dismissed: `auditor_unavailable_d4` (code-quality D4, mutation testing), for B-380
7
+ (`sdk-memory-ceiling`) and for any item whose files sit outside the Stryker scope below
8
+
9
+ ## Context
10
+
11
+ `stryker.config.json` at the repository root came in with adeba4b1 as a measurement spike for
12
+ B-026. It was never wired into a gate. Its scope is narrow on purpose:
13
+
14
+ - `mutate` is `src/internal/llm/**/*.ts`, nothing else;
15
+ - it is meant to run from `packages/sdk` (`pnpm --filter exec`), so `jsonReporter.fileName` is
16
+ `../../reports/mutation/slice.json`;
17
+ - `inPlace: true` is required because `packages/sdk/vitest.setup.ts` imports
18
+ `../../tools/preflight-native-bindings.mjs`, which escapes the package, so a sandbox copy fails
19
+ before it collects a test. The same commit records that `inPlace` restores file content but not
20
+ file mode, which strips the executable bit from `bin/*.mjs` entry points.
21
+
22
+ The kit's code-quality detector D4 looks for a Stryker config in the manifest directory (the
23
+ repository root here), runs `stryker run --reporters json` from there, and reads
24
+ `reports/mutation/mutation.json`. With this config that file is never written, so D4 reports
25
+ `auditor_unavailable_d4` ("stryker ran but wrote no report"). B-380 was the first item in this
26
+ repository to reach the code-quality audit, and none of its files is under `src/internal/llm`.
27
+ Nothing in B-380 touched the config.
28
+
29
+ ## Decision
30
+
31
+ Mutation testing in theokit-sdk is the B-026 spike over `src/internal/llm` and nothing more, until
32
+ a separate backlog item extends it. D4 cannot measure code outside that scope today, so an item
33
+ whose files are outside it carries no mutation measurement, and its audit's
34
+ `auditor_unavailable_d4` soft cap is dismissed by this record. Extending Stryker is registered as
35
+ its own backlog item (`repo: theokit-sdk`).
36
+
37
+ This dismisses the cap. It does not claim the uncovered code is well tested: the unit and
38
+ integration suites are still the only evidence for it.
39
+
40
+ ## Alternatives rejected
41
+
42
+ - Extend the Stryker config inside B-380, writing the report to `reports/mutation/mutation.json`
43
+ and mutating the memory files. Rejected because it widens an item that is already executing.
44
+ Runs over `packages/sdk` took minutes per file set, surviving mutants could need new tests, and
45
+ the `inPlace` file-mode risk above would come with it, all for work B-380 never scoped.
46
+ - Treat the mismatch as a kit defect (D4 runs from the repository root while the config expects
47
+ `packages/sdk`), report it to the kit and hold B-380 until it is fixed. Rejected because the
48
+ config is a spike that was never adopted as a gate. A kit fix would run Stryker against the same
49
+ `src/internal/llm` glob and still measure nothing B-380 changed.
50
+
51
+ ## Consequences
52
+
53
+ - Every item outside `src/internal/llm` reaches review with D4 unmeasured and FAIL_SOFT, admitted
54
+ only through this record.
55
+ - The record stops applying once the backlog item that extends Stryker lands. At that point it
56
+ should be marked superseded.
@@ -6,7 +6,7 @@ Branch on `code`, never on the message: messages carry context (an id, a path, a
6
6
 
7
7
  **Transport codes vs the rest.** `ErrorCode` in `errors.ts` is the small canonical union a provider failure maps onto — the codes marked *transport* below. Everything else is raised by a specific subsystem at a specific place, and a `catch` that only handles the union will meet them anyway.
8
8
 
9
- 254 distinct code(s).
9
+ 256 distinct code(s).
10
10
 
11
11
  | Code | Kind | Raised by | Sites |
12
12
  |---|---|---|---|
@@ -60,7 +60,7 @@ Branch on `code`, never on the message: messages carry context (an id, a path, a
60
60
  | `cron_missing_message` | domain | ConfigurationError | `packages/sdk/src/cron.ts:233` +1 |
61
61
  | `cron_no_target` | domain | ConfigurationError | `packages/sdk/src/cron.ts:221` +1 |
62
62
  | `cron_workflow_message` | domain | ConfigurationError | `packages/sdk/src/cron.ts:227` |
63
- | `duplicate_skill_name` | domain | ConfigurationError | `packages/sdk/src/define-skill-read-tool.ts:155` |
63
+ | `duplicate_skill_name` | domain | ConfigurationError | `packages/sdk/src/define-skill-read-tool.ts:166` |
64
64
  | `duplicate_tool_name` | domain | ConfigurationError | `packages/sdk/src/internal/runtime/validation/validate-agent-options.ts:203` |
65
65
  | `effective_tools_expected_options` | domain | ConfigurationError | `packages/sdk/src/internal/runtime/validation/effective-tools.ts:102` +1 |
66
66
  | `embedding_dimension_mismatch` | domain | ConfigurationError | `packages/sdk/src/internal/memory/lance-index.ts:146` +1 |
@@ -100,7 +100,7 @@ Branch on `code`, never on the message: messages carry context (an id, a path, a
100
100
  | `invalid_filename_id` | domain | ConfigurationError | `packages/sdk/src/internal/security/path-guard.ts:486` |
101
101
  | `invalid_identifier` | domain | ConfigurationError | `packages/sdk/src/internal/security/path-guard.ts:434` +2 |
102
102
  | `invalid_input` | domain | MemoryAdapterError | `packages/memory-honcho/src/adapter.ts:98` +9 |
103
- | `invalid_max_iterations` | domain | ConfigurationError | `packages/sdk/src/internal/local-agent/real-local-run.ts:73` |
103
+ | `invalid_max_iterations` | domain | ConfigurationError | `packages/sdk/src/internal/local-agent/real-local-run.ts:94` |
104
104
  | `invalid_memory_backend` | domain | ConfigurationError | `packages/sdk/src/internal/memory/index-manager-dispatch.ts:24` +1 |
105
105
  | `invalid_memory_ceiling` | domain | ConfigurationError | `packages/sdk/src/internal/memory/storage/fact-ceiling.ts:23` +1 |
106
106
  | `invalid_memory_directory` | domain | ConfigurationError | `packages/sdk/src/internal/memory/storage/memory-root.ts:151` |
@@ -108,6 +108,7 @@ Branch on `code`, never on the message: messages carry context (an id, a path, a
108
108
  | `invalid_model_param` | domain | ConfigurationError | `packages/sdk/src/internal/runtime/model-selection.ts:43` +1 |
109
109
  | `invalid_model_selection` | domain | ConfigurationError | `packages/sdk/src/internal/runtime/model-selection.ts:25` |
110
110
  | `invalid_processor_options` | domain | ConfigurationError | `packages/sdk/src/built-in-processors.ts:86` |
111
+ | `invalid_prune_tool_results` | domain | ConfigurationError | `packages/sdk/src/internal/agent-loop/prune-tool-results.ts:78` |
111
112
  | `invalid_redaction_pattern` | domain | ConfigurationError | `packages/sdk/src/security.ts:84` |
112
113
  | `invalid_request` | domain | — | `packages/sdk/src/internal/error-mappers/vertex.ts:53` +1 |
113
114
  | `invalid_retry_config` | domain | ConfigurationError | `packages/sdk/src/internal/retry/with-retry.ts:67` |
@@ -142,12 +143,13 @@ Branch on `code`, never on the message: messages carry context (an id, a path, a
142
143
  | `migration_destination_exists` | domain | ConfigurationError | `packages/sdk/src/internal/memory/migrate-sqlite-to-lance.ts:159` +1 |
143
144
  | `missing_api_key` | domain | AuthenticationError, ConfigurationError | `packages/sdk/src/internal/agent/helpers.ts:222` +2 |
144
145
  | `missing_credential` | domain | AuthenticationError | `packages/sdk/src/internal/providers/builtin/openai-chatgpt.ts:72` |
145
- | `missing_frontmatter` | domain | ConfigurationError | `packages/sdk/src/internal/runtime/skills/skill-frontmatter.ts:88` |
146
+ | `missing_frontmatter` | domain | ConfigurationError | `packages/sdk/src/internal/runtime/skills/skill-frontmatter.ts:93` |
146
147
  | `missing_model` | domain | ConfigurationError | `packages/sdk/src/internal/runtime/validation/validate-agent-options.ts:37` |
147
148
  | `model_unavailable` | domain | buildErrorMetadata | `packages/sdk/src/internal/error-mappers/ollama.ts:75` +2 |
148
149
  | `network` | domain | MemoryAdapterError, buildErrorMetadata | `packages/memory-honcho/src/adapter.ts:258` +5 |
149
150
  | `network_error` | domain | NetworkError | `packages/sdk/src/internal/http.ts:99` |
150
- | `no_api_key` | domain | ConfigurationError | `packages/sdk-tools/src/web-search-brave.ts:50` |
151
+ | `no_api_key` | domain | ConfigurationError | `packages/sdk-tools/src/web-search-brave.ts:50` +1 |
152
+ | `no_base_url` | domain | ConfigurationError | `packages/sdk-tools/src/web-search-searxng.ts:35` |
151
153
  | `no_memory_adapter` | domain | ConfigurationError | `packages/sdk/src/internal/local-agent/local-agent-memory-direct.ts:141` +2 |
152
154
  | `no_such_session` | domain | NoSuchSessionError | `packages/sdk/src/interactive/types.ts:41` +1 |
153
155
  | `no_text_answer` | domain | — | `packages/sdk/src/errors.ts:656` |
@@ -194,7 +196,7 @@ Branch on `code`, never on the message: messages carry context (an id, a path, a
194
196
  | `sandbox_derived_helper_failed` | domain | ConfigurationError | `packages/sdk/src/sandbox/types.ts:223` +1 |
195
197
  | `sandbox_not_available` | domain | SandboxNotAvailableError | `packages/sdk/src/sandbox/types.ts:108` +1 |
196
198
  | `sandbox_security` | domain | SandboxSecurityError | `packages/sdk/src/sandbox/types.ts:89` +1 |
197
- | `schema_invalid` | domain | ConfigurationError | `packages/sdk/src/internal/runtime/skills/skill-frontmatter.ts:99` +5 |
199
+ | `schema_invalid` | domain | ConfigurationError | `packages/sdk/src/internal/runtime/skills/skill-frontmatter.ts:104` +5 |
198
200
  | `server_error` | domain | buildErrorMetadata | `packages/sdk/src/internal/error-mappers/ollama.ts:92` +1 |
199
201
  | `session_busy` | domain | — | `packages/sdk/src/internal/persistence/session-writer.ts:56` +1 |
200
202
  | `sql_injection_blocked` | domain | ConfigurationError | `packages/sdk/src/internal/memory/lance-index.ts:256` +3 |
@@ -4,7 +4,7 @@ Every public symbol the TheoKit workspace publishes, and the exact specifier to
4
4
 
5
5
  A symbol listed under two specifiers is reachable from both, but that does NOT make the two interchangeable: a class emitted separately into a subpath entry is a distinct nominal type from the one in the root bundle, so passing one where the other is expected fails on a private field. When a symbol appears twice, import it and everything it is passed to from the SAME specifier.
6
6
 
7
- 1259 export(s) across 50 entry point(s).
7
+ 1272 export(s) across 53 entry point(s).
8
8
 
9
9
  ## `@theokit/acp`
10
10
 
@@ -308,6 +308,7 @@ A symbol listed under two specifiers is reachable from both, but that does NOT m
308
308
  | `ProviderRoutingSettings` | interface | Provider routing configuration accepted by `Agent.create()` via {@link AgentOptions.providers } . |
309
309
  | `ProviderTransform` | interface | M41 — the one OPTIONAL behavior seam on a provider profile. |
310
310
  | `ProviderTransformContext` | interface | M41 (agent-builder provider framework) — the context a provider's `transform` receives per request. |
311
+ | `PruneToolResultsOptions` | interface | B-564 — how old a tool result may get, in tool rounds, before its content is pruned from the next LLM request (`SendOptions.pruneToolResults`). |
311
312
  | `RateLimitError` | class | Too many requests or usage limits exceeded. |
312
313
  | `readManagedSettings` | function | Read the deployed policy, or `{}` when none is. |
313
314
  | `readSessionMessages` | function | Read the messages a session already contains, for a surface that needs to re-render it. |
@@ -852,6 +853,15 @@ A symbol listed under two specifiers is reachable from both, but that does NOT m
852
853
  | `GithubLinkKind` | type | What a previewed GitHub URL points at, taken from its path. |
853
854
  | `GithubLinkPreviewResult` | type | The JSON the `github_link_preview` tool returns: a preview, or a typed failure. |
854
855
 
856
+ ## `@theokit/sdk-tools/goal`
857
+
858
+ | Symbol | Kind | Summary |
859
+ |---|---|---|
860
+ | `createGoalTool` | function | Build the `goal` tool: the agent's one standing objective for a session, read back in later turns. |
861
+ | `CreateGoalToolOptions` | interface | Options for {@link createGoalTool } . |
862
+ | `GoalState` | interface | A session's goal: its text, whether it is still active, and when it was set and completed. |
863
+ | `GoalTool` | interface | The `goal` tool object. |
864
+
855
865
  ## `@theokit/sdk-tools/read-pdf`
856
866
 
857
867
  | Symbol | Kind | Summary |
@@ -870,6 +880,24 @@ A symbol listed under two specifiers is reachable from both, but that does NOT m
870
880
  | `LogbookEntry` | type | One line of the log. |
871
881
  | `ScreenshotLogbookResult` | type | The tool's result, before it is serialised to the JSON string the handler returns. |
872
882
 
883
+ ## `@theokit/sdk-tools/structured-output`
884
+
885
+ | Symbol | Kind | Summary |
886
+ |---|---|---|
887
+ | `createStructuredOutputTool` | function | Build a `structured_output` tool over `schema`. |
888
+ | `CreateStructuredOutputToolOptions` | interface | Options for {@link createStructuredOutputTool } . |
889
+ | `StructuredOutputIssue` | interface | One validation issue: the dotted path to the field (empty for the root) and zod's message. |
890
+ | `StructuredOutputResult` | type | The tool's result, before it is serialised to the JSON string the handler returns. |
891
+
892
+ ## `@theokit/sdk-tools/web-search-adapters`
893
+
894
+ | Symbol | Kind | Summary |
895
+ |---|---|---|
896
+ | `createSearxngWebSearchAdapter` | function | Build a `WebSearchCallback` backed by a SearXNG instance. |
897
+ | `CreateSearxngWebSearchAdapterOptions` | interface | Options for {@link createSearxngWebSearchAdapter } . |
898
+ | `createTavilyWebSearchAdapter` | function | Build a `WebSearchCallback` backed by the Tavily Search API. |
899
+ | `CreateTavilyWebSearchAdapterOptions` | interface | Options for {@link createTavilyWebSearchAdapter } . |
900
+
873
901
  ## `@theokit/sdk/a2a`
874
902
 
875
903
  | Symbol | Kind | Summary |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theokit/sdk",
3
- "version": "5.13.0",
3
+ "version": "5.14.0",
4
4
  "description": "TypeScript SDK for the Theo agent harness — same surface, local or cloud.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/usetheokit/theokit-sdk#readme",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/internal/auth/oauth-engine.ts","../src/internal/auth/resolve-credential.ts"],"names":["fresh"],"mappings":"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","file":"chunk-3FSBE4PS.js","sourcesContent":["/**\n * M42 — the SDK OAuth engine. Promoted DOWN from agent-builder's hardened `agents/lib/oauth.ts` +\n * `oauth-device.ts` (M37), generalized to `provider: string` + a caller-supplied {@link CredentialStoreConfig}\n * (no hardcoded client IDs — the caller declares its provider's endpoints). Ported VERBATIM (ADR D3): the\n * refresh coalescing lock, the no-token-in-error discipline, and the RFC 8628 device grant.\n *\n * Reuses the SDK's typed error taxonomy (`AuthCallbackError` from `server/auth`) for exchange/refresh\n * failures. All network I/O + clock + sleep are INJECTED so the flow is deterministic.\n *\n * The device flows follow their published specs: the RFC 8628 device grant (device_code →\n * authorization_pending / slow_down) and OpenAI's two-step headless flow with JWT-claim account attribution.\n *\n * @internal\n */\nimport { AuthCallbackError } from \"../../server/auth/errors.js\";\nimport { authFilePath, readStoredOAuth, writeCredential } from \"./credential-store.js\";\nimport type {\n CredentialStoreConfig,\n HttpDeps,\n OAuthProviderConfig,\n OAuthTokens,\n ResolvedCredential,\n} from \"./types.js\";\n\n/** Refresh a token this many ms BEFORE it actually expires (avoid a last-second, mid-request expiry). */\nconst REFRESH_SKEW_MS = 60_000;\n\n/** Parse an OAuth token endpoint response into our token triple. Throws a typed error on a bad shape. */\nfunction parseTokenResponse(body: unknown, now: number): OAuthTokens {\n const b = body as {\n access_token?: unknown;\n refresh_token?: unknown;\n expires_in?: unknown;\n account_id?: unknown;\n };\n if (typeof b.access_token !== \"string\" || b.access_token.length === 0) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n \"token response had no access_token\",\n );\n }\n if (typeof b.refresh_token !== \"string\" || b.refresh_token.length === 0) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n \"token response had no refresh_token\",\n );\n }\n const expiresIn = typeof b.expires_in === \"number\" ? b.expires_in : 3600;\n return {\n access: b.access_token,\n refresh: b.refresh_token,\n expires: now + expiresIn * 1000,\n ...(typeof b.account_id === \"string\" ? { accountId: b.account_id } : {}),\n };\n}\n\n/** POST a form-encoded grant to the token endpoint; return the parsed JSON or throw a typed error. */\nasync function postGrant(\n config: OAuthProviderConfig,\n form: Record<string, string>,\n deps: HttpDeps,\n): Promise<OAuthTokens> {\n let res: Response;\n try {\n res = await deps.fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\", accept: \"application/json\" },\n body: new URLSearchParams(form).toString(),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `token endpoint request failed: ${(err as Error).message}`,\n );\n }\n if (!res.ok) {\n // The body may carry an OAuth `error` code, but never echo it verbatim — it can contain a token in\n // some providers' error payloads. Report the status only.\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `token endpoint returned HTTP ${res.status}`,\n );\n }\n let json: unknown;\n try {\n json = await res.json();\n } catch {\n // Do NOT echo the parser message — on a 200 non-JSON body it embeds a snippet of the response, which\n // in a token context can contain access/refresh material.\n throw new AuthCallbackError(\"oauth_token_exchange_failed\", \"token response was not valid JSON\");\n }\n return parseTokenResponse(json, deps.now());\n}\n\n/** Exchange an authorization `code` (+ PKCE verifier) for the token pair. */\nexport function exchangeCode(\n config: OAuthProviderConfig,\n input: { code: string; verifier: string },\n deps: HttpDeps,\n): Promise<OAuthTokens> {\n return postGrant(\n config,\n {\n grant_type: \"authorization_code\",\n code: input.code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n code_verifier: input.verifier,\n },\n deps,\n );\n}\n\n/** Swap a refresh token for a fresh access (and possibly rotated refresh) token. */\nexport function refreshOAuthTokens(\n config: OAuthProviderConfig,\n refresh: string,\n deps: HttpDeps,\n): Promise<OAuthTokens> {\n return postGrant(\n config,\n { grant_type: \"refresh_token\", refresh_token: refresh, client_id: config.clientId },\n deps,\n );\n}\n\n/** Persist a token triple to the store's oauth variant through the hardened 0600 writer. */\nexport function persistOAuthTokens(\n provider: string,\n tokens: OAuthTokens,\n store: CredentialStoreConfig,\n env: Record<string, string | undefined> = {},\n): string {\n return writeCredential(\n {\n type: \"oauth\",\n provider,\n access: tokens.access,\n refresh: tokens.refresh,\n expires: tokens.expires,\n ...(tokens.accountId !== undefined ? { account_id: tokens.accountId } : {}),\n },\n store,\n env,\n );\n}\n\n/**\n * In-flight refreshes, keyed by the auth-file path. Refresh tokens ROTATE (single-use) on many providers,\n * so two concurrent turns must NOT both POST the same refresh token — the second would present an\n * already-consumed token and fail. This coalesces concurrent refreshes of the same store onto one promise.\n * Process-local — the single-credential store is per-user; a cross-process lock would be over-engineering.\n */\nconst inFlightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Return a credential guaranteed fresh enough to use. An `api` credential passes through untouched. An\n * `oauth` credential still valid (beyond the skew window) passes through; an expired one is refreshed,\n * re-persisted 0600, and returned with the new access token. Concurrent refreshes of the same store are\n * coalesced (single-use refresh tokens); the rejected promise is evicted so a failure is not cached-poison.\n */\nexport async function ensureFreshCredential(\n resolved: ResolvedCredential,\n opts: {\n config: OAuthProviderConfig;\n store: CredentialStoreConfig;\n env?: Record<string, string | undefined>;\n },\n deps: HttpDeps,\n): Promise<ResolvedCredential> {\n if (resolved.kind !== \"oauth\") return resolved;\n\n const now = deps.now();\n if (resolved.expiresAt !== undefined && resolved.expiresAt > now + REFRESH_SKEW_MS) {\n return resolved; // still valid — no network, no rewrite\n }\n\n const env = opts.env ?? {};\n const path = authFilePath(opts.store, env);\n let refresh = inFlightRefresh.get(path);\n if (refresh === undefined) {\n refresh = (async (): Promise<OAuthTokens> => {\n const stored = readStoredOAuth(opts.store, env);\n if (stored === undefined) {\n // The resolved credential said oauth but the store no longer holds it — do not fabricate; surface it.\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n \"no stored oauth credential to refresh\",\n );\n }\n const fresh = await refreshOAuthTokens(opts.config, stored.refresh, deps);\n // M43 D4 fix #1 — the OAuth refresh response returns JWTs, not a top-level `account_id`, so\n // `parseTokenResponse` leaves `fresh.accountId` undefined. Prefer a freshly-derived id, else PRESERVE\n // the stored one (account_id is stable) — otherwise the Codex `ChatGPT-Account-Id` header empties after\n // the first refresh and the backend 401/403s.\n const merged: OAuthTokens = {\n ...fresh,\n accountId: fresh.accountId ?? stored.account_id,\n };\n persistOAuthTokens(resolved.provider, merged, opts.store, env);\n return merged;\n })();\n inFlightRefresh.set(path, refresh);\n refresh.finally(() => inFlightRefresh.delete(path)).catch(() => {});\n }\n\n const fresh = await refresh;\n return {\n kind: \"oauth\",\n provider: resolved.provider,\n apiKey: fresh.access,\n source: resolved.source,\n inferred: false,\n expiresAt: fresh.expires,\n };\n}\n","/**\n * M42 — `resolveCredential`: the public composition entrypoint over the credential store + oauth engine.\n * Given a provider name + a {@link CredentialStoreConfig} (and, for oauth, an {@link OAuthProviderConfig}),\n * read the store and return a FRESH `ResolvedCredential` — an api credential passes through by identity; an\n * oauth credential is transparently refreshed when its access token is within the skew window. Returns\n * `undefined` when the store holds no credential for `provider` (the caller then falls back to env / apiKeys).\n *\n * Mirrors agent-builder's `resolveFreshCredential` composition (store read → oauth → ensureFreshCredential),\n * defaulting `fetch`/`now` to globals. This is the async surface an oauth provider's M41 `transform.fetch`\n * calls at stream time, so a mid-turn token expiry refreshes without rebuilding the agent.\n *\n * Re-exported from `@theokit/sdk/auth`, and therefore PUBLIC, under semver.\n *\n * NOTE — no internal-visibility tag in this block. `tsconfig.base.json` sets `stripInternal: true`,\n * and TypeScript scans EVERY leading comment range of the declaration that follows, including the\n * import right below this one. The tag that used to sit here deleted that import from the emitted\n * `.d.ts`, leaving the types it binds unresolvable for any consumer running type-aware lint\n * (usetheodev/theokit-sdk#283 records the same trap on a declaration).\n */\n\nimport { boundFetch } from \"../runtime-fetch.js\";\nimport { authFilePath, readAuthFile } from \"./credential-store.js\";\nimport { ensureFreshCredential } from \"./oauth-engine.js\";\nimport type {\n CredentialStoreConfig,\n HttpDeps,\n OAuthProviderConfig,\n ResolvedCredential,\n} from \"./types.js\";\n\n/**\n * Input to `resolveCredential`: which provider, which store, and what to inject.\n *\n * `env` and `deps` exist so nothing here reads ambient state. Omitting `env` does NOT fall back to\n * `process.env` — the store's `homeEnvVar` override simply goes unread, so a credential written\n * under a relocated home resolves as absent rather than as an error.\n *\n * `oauth` is required only to REFRESH an oauth credential. Without it an expired oauth entry cannot\n * be renewed, and an api-key-only store needs it never.\n *\n * PUBLIC and under semver, despite the `internal/` directory: `src/auth/index.ts` re-exports this\n * type, and `package.json` publishes that barrel as the '@theokit/sdk/auth' subpath. The directory\n * name is not the authority — the `exports` map is. The semver-exempt class is the sub-paths\n * declared under '@theokit/sdk/internal/*' ('internal/persistence', 'internal/security',\n * 'internal/memory-adapters'); no `exports` entry reaches this file that way.\n */\nexport interface ResolveCredentialOptions {\n /** The provider whose stored credential to resolve. */\n provider: string;\n /** Where the credential store lives. */\n store: CredentialStoreConfig;\n /** Env for the store's optional `homeEnvVar` override. Never an ambient read. */\n env?: Record<string, string | undefined>;\n /** Required to refresh an oauth credential; omit for an api-only store. */\n oauth?: OAuthProviderConfig;\n /** Injected HTTP + clock for deterministic tests; default globals. */\n deps?: { fetch?: typeof fetch; now?: () => number };\n}\n\n/** Resolve (and, when a config is supplied, refresh) the oauth variant for the requested provider. */\nasync function resolveOAuth(\n stored: { provider: string; access: string; expires: number },\n path: string,\n opts: ResolveCredentialOptions,\n env: Record<string, string | undefined>,\n): Promise<ResolvedCredential | undefined> {\n if (stored.provider !== opts.provider) return undefined;\n const base: ResolvedCredential = {\n kind: \"oauth\",\n provider: opts.provider,\n apiKey: stored.access,\n source: path,\n inferred: false,\n expiresAt: stored.expires,\n };\n if (opts.oauth === undefined) return base; // no config to refresh with — return the stored access token\n const deps: HttpDeps = {\n fetch: boundFetch(opts.deps?.fetch),\n now: opts.deps?.now ?? (() => Date.now()),\n };\n return ensureFreshCredential(base, { config: opts.oauth, store: opts.store, env }, deps);\n}\n\n/**\n * Resolve a fresh credential for `provider` from the store, or `undefined` if the store holds none for it.\n * oauth credentials are auto-refreshed when `opts.oauth` is supplied; api credentials pass through.\n */\nexport async function resolveCredential(\n opts: ResolveCredentialOptions,\n): Promise<ResolvedCredential | undefined> {\n const env = opts.env ?? {};\n const stored = readAuthFile(opts.store, env);\n if (stored === undefined) return undefined;\n\n const path = authFilePath(opts.store, env);\n\n if (stored.type === \"oauth\") {\n return resolveOAuth(stored, path, opts, env);\n }\n\n // api variant: require the stored `provider` to MATCH the requested provider (M42 review MEDIUM-2).\n // The generic SDK cannot infer a provider from a key prefix (that was agent-builder app policy), so a\n // provider-less or mismatched stored key is NOT attributed to the requested provider — otherwise a legacy\n // `{api_key:\"sk-ant-…\"}` file could be POSTed to the wrong vendor's endpoint (cross-vendor key exposure).\n // Fail-closed: credential resolution never attributes a key across providers.\n if (stored.api_key.length === 0) return undefined;\n if (stored.provider !== opts.provider) return undefined;\n return {\n kind: \"api\",\n provider: opts.provider,\n apiKey: stored.api_key,\n source: path,\n inferred: false,\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/internal/auth/oauth-engine.ts","../src/internal/auth/resolve-credential.ts"],"names":["AuthCallbackError","writeCredential","authFilePath","readStoredOAuth","fresh","boundFetch","readAuthFile"],"mappings":"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","file":"chunk-5A4UFBHW.cjs","sourcesContent":["/**\n * M42 — the SDK OAuth engine. Promoted DOWN from agent-builder's hardened `agents/lib/oauth.ts` +\n * `oauth-device.ts` (M37), generalized to `provider: string` + a caller-supplied {@link CredentialStoreConfig}\n * (no hardcoded client IDs — the caller declares its provider's endpoints). Ported VERBATIM (ADR D3): the\n * refresh coalescing lock, the no-token-in-error discipline, and the RFC 8628 device grant.\n *\n * Reuses the SDK's typed error taxonomy (`AuthCallbackError` from `server/auth`) for exchange/refresh\n * failures. All network I/O + clock + sleep are INJECTED so the flow is deterministic.\n *\n * The device flows follow their published specs: the RFC 8628 device grant (device_code →\n * authorization_pending / slow_down) and OpenAI's two-step headless flow with JWT-claim account attribution.\n *\n * @internal\n */\nimport { AuthCallbackError } from \"../../server/auth/errors.js\";\nimport { authFilePath, readStoredOAuth, writeCredential } from \"./credential-store.js\";\nimport type {\n CredentialStoreConfig,\n HttpDeps,\n OAuthProviderConfig,\n OAuthTokens,\n ResolvedCredential,\n} from \"./types.js\";\n\n/** Refresh a token this many ms BEFORE it actually expires (avoid a last-second, mid-request expiry). */\nconst REFRESH_SKEW_MS = 60_000;\n\n/** Parse an OAuth token endpoint response into our token triple. Throws a typed error on a bad shape. */\nfunction parseTokenResponse(body: unknown, now: number): OAuthTokens {\n const b = body as {\n access_token?: unknown;\n refresh_token?: unknown;\n expires_in?: unknown;\n account_id?: unknown;\n };\n if (typeof b.access_token !== \"string\" || b.access_token.length === 0) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n \"token response had no access_token\",\n );\n }\n if (typeof b.refresh_token !== \"string\" || b.refresh_token.length === 0) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n \"token response had no refresh_token\",\n );\n }\n const expiresIn = typeof b.expires_in === \"number\" ? b.expires_in : 3600;\n return {\n access: b.access_token,\n refresh: b.refresh_token,\n expires: now + expiresIn * 1000,\n ...(typeof b.account_id === \"string\" ? { accountId: b.account_id } : {}),\n };\n}\n\n/** POST a form-encoded grant to the token endpoint; return the parsed JSON or throw a typed error. */\nasync function postGrant(\n config: OAuthProviderConfig,\n form: Record<string, string>,\n deps: HttpDeps,\n): Promise<OAuthTokens> {\n let res: Response;\n try {\n res = await deps.fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\", accept: \"application/json\" },\n body: new URLSearchParams(form).toString(),\n });\n } catch (err) {\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `token endpoint request failed: ${(err as Error).message}`,\n );\n }\n if (!res.ok) {\n // The body may carry an OAuth `error` code, but never echo it verbatim — it can contain a token in\n // some providers' error payloads. Report the status only.\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n `token endpoint returned HTTP ${res.status}`,\n );\n }\n let json: unknown;\n try {\n json = await res.json();\n } catch {\n // Do NOT echo the parser message — on a 200 non-JSON body it embeds a snippet of the response, which\n // in a token context can contain access/refresh material.\n throw new AuthCallbackError(\"oauth_token_exchange_failed\", \"token response was not valid JSON\");\n }\n return parseTokenResponse(json, deps.now());\n}\n\n/** Exchange an authorization `code` (+ PKCE verifier) for the token pair. */\nexport function exchangeCode(\n config: OAuthProviderConfig,\n input: { code: string; verifier: string },\n deps: HttpDeps,\n): Promise<OAuthTokens> {\n return postGrant(\n config,\n {\n grant_type: \"authorization_code\",\n code: input.code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n code_verifier: input.verifier,\n },\n deps,\n );\n}\n\n/** Swap a refresh token for a fresh access (and possibly rotated refresh) token. */\nexport function refreshOAuthTokens(\n config: OAuthProviderConfig,\n refresh: string,\n deps: HttpDeps,\n): Promise<OAuthTokens> {\n return postGrant(\n config,\n { grant_type: \"refresh_token\", refresh_token: refresh, client_id: config.clientId },\n deps,\n );\n}\n\n/** Persist a token triple to the store's oauth variant through the hardened 0600 writer. */\nexport function persistOAuthTokens(\n provider: string,\n tokens: OAuthTokens,\n store: CredentialStoreConfig,\n env: Record<string, string | undefined> = {},\n): string {\n return writeCredential(\n {\n type: \"oauth\",\n provider,\n access: tokens.access,\n refresh: tokens.refresh,\n expires: tokens.expires,\n ...(tokens.accountId !== undefined ? { account_id: tokens.accountId } : {}),\n },\n store,\n env,\n );\n}\n\n/**\n * In-flight refreshes, keyed by the auth-file path. Refresh tokens ROTATE (single-use) on many providers,\n * so two concurrent turns must NOT both POST the same refresh token — the second would present an\n * already-consumed token and fail. This coalesces concurrent refreshes of the same store onto one promise.\n * Process-local — the single-credential store is per-user; a cross-process lock would be over-engineering.\n */\nconst inFlightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Return a credential guaranteed fresh enough to use. An `api` credential passes through untouched. An\n * `oauth` credential still valid (beyond the skew window) passes through; an expired one is refreshed,\n * re-persisted 0600, and returned with the new access token. Concurrent refreshes of the same store are\n * coalesced (single-use refresh tokens); the rejected promise is evicted so a failure is not cached-poison.\n */\nexport async function ensureFreshCredential(\n resolved: ResolvedCredential,\n opts: {\n config: OAuthProviderConfig;\n store: CredentialStoreConfig;\n env?: Record<string, string | undefined>;\n },\n deps: HttpDeps,\n): Promise<ResolvedCredential> {\n if (resolved.kind !== \"oauth\") return resolved;\n\n const now = deps.now();\n if (resolved.expiresAt !== undefined && resolved.expiresAt > now + REFRESH_SKEW_MS) {\n return resolved; // still valid — no network, no rewrite\n }\n\n const env = opts.env ?? {};\n const path = authFilePath(opts.store, env);\n let refresh = inFlightRefresh.get(path);\n if (refresh === undefined) {\n refresh = (async (): Promise<OAuthTokens> => {\n const stored = readStoredOAuth(opts.store, env);\n if (stored === undefined) {\n // The resolved credential said oauth but the store no longer holds it — do not fabricate; surface it.\n throw new AuthCallbackError(\n \"oauth_token_exchange_failed\",\n \"no stored oauth credential to refresh\",\n );\n }\n const fresh = await refreshOAuthTokens(opts.config, stored.refresh, deps);\n // M43 D4 fix #1 — the OAuth refresh response returns JWTs, not a top-level `account_id`, so\n // `parseTokenResponse` leaves `fresh.accountId` undefined. Prefer a freshly-derived id, else PRESERVE\n // the stored one (account_id is stable) — otherwise the Codex `ChatGPT-Account-Id` header empties after\n // the first refresh and the backend 401/403s.\n const merged: OAuthTokens = {\n ...fresh,\n accountId: fresh.accountId ?? stored.account_id,\n };\n persistOAuthTokens(resolved.provider, merged, opts.store, env);\n return merged;\n })();\n inFlightRefresh.set(path, refresh);\n refresh.finally(() => inFlightRefresh.delete(path)).catch(() => {});\n }\n\n const fresh = await refresh;\n return {\n kind: \"oauth\",\n provider: resolved.provider,\n apiKey: fresh.access,\n source: resolved.source,\n inferred: false,\n expiresAt: fresh.expires,\n };\n}\n","/**\n * M42 — `resolveCredential`: the public composition entrypoint over the credential store + oauth engine.\n * Given a provider name + a {@link CredentialStoreConfig} (and, for oauth, an {@link OAuthProviderConfig}),\n * read the store and return a FRESH `ResolvedCredential` — an api credential passes through by identity; an\n * oauth credential is transparently refreshed when its access token is within the skew window. Returns\n * `undefined` when the store holds no credential for `provider` (the caller then falls back to env / apiKeys).\n *\n * Mirrors agent-builder's `resolveFreshCredential` composition (store read → oauth → ensureFreshCredential),\n * defaulting `fetch`/`now` to globals. This is the async surface an oauth provider's M41 `transform.fetch`\n * calls at stream time, so a mid-turn token expiry refreshes without rebuilding the agent.\n *\n * Re-exported from `@theokit/sdk/auth`, and therefore PUBLIC, under semver.\n *\n * NOTE — no internal-visibility tag in this block. `tsconfig.base.json` sets `stripInternal: true`,\n * and TypeScript scans EVERY leading comment range of the declaration that follows, including the\n * import right below this one. The tag that used to sit here deleted that import from the emitted\n * `.d.ts`, leaving the types it binds unresolvable for any consumer running type-aware lint\n * (usetheodev/theokit-sdk#283 records the same trap on a declaration).\n */\n\nimport { boundFetch } from \"../runtime-fetch.js\";\nimport { authFilePath, readAuthFile } from \"./credential-store.js\";\nimport { ensureFreshCredential } from \"./oauth-engine.js\";\nimport type {\n CredentialStoreConfig,\n HttpDeps,\n OAuthProviderConfig,\n ResolvedCredential,\n} from \"./types.js\";\n\n/**\n * Input to `resolveCredential`: which provider, which store, and what to inject.\n *\n * `env` and `deps` exist so nothing here reads ambient state. Omitting `env` does NOT fall back to\n * `process.env` — the store's `homeEnvVar` override simply goes unread, so a credential written\n * under a relocated home resolves as absent rather than as an error.\n *\n * `oauth` is required only to REFRESH an oauth credential. Without it an expired oauth entry cannot\n * be renewed, and an api-key-only store needs it never.\n *\n * PUBLIC and under semver, despite the `internal/` directory: `src/auth/index.ts` re-exports this\n * type, and `package.json` publishes that barrel as the '@theokit/sdk/auth' subpath. The directory\n * name is not the authority — the `exports` map is. The semver-exempt class is the sub-paths\n * declared under '@theokit/sdk/internal/*' ('internal/persistence', 'internal/security',\n * 'internal/memory-adapters'); no `exports` entry reaches this file that way.\n */\nexport interface ResolveCredentialOptions {\n /** The provider whose stored credential to resolve. */\n provider: string;\n /** Where the credential store lives. */\n store: CredentialStoreConfig;\n /** Env for the store's optional `homeEnvVar` override. Never an ambient read. */\n env?: Record<string, string | undefined>;\n /** Required to refresh an oauth credential; omit for an api-only store. */\n oauth?: OAuthProviderConfig;\n /** Injected HTTP + clock for deterministic tests; default globals. */\n deps?: { fetch?: typeof fetch; now?: () => number };\n}\n\n/** Resolve (and, when a config is supplied, refresh) the oauth variant for the requested provider. */\nasync function resolveOAuth(\n stored: { provider: string; access: string; expires: number },\n path: string,\n opts: ResolveCredentialOptions,\n env: Record<string, string | undefined>,\n): Promise<ResolvedCredential | undefined> {\n if (stored.provider !== opts.provider) return undefined;\n const base: ResolvedCredential = {\n kind: \"oauth\",\n provider: opts.provider,\n apiKey: stored.access,\n source: path,\n inferred: false,\n expiresAt: stored.expires,\n };\n if (opts.oauth === undefined) return base; // no config to refresh with — return the stored access token\n const deps: HttpDeps = {\n fetch: boundFetch(opts.deps?.fetch),\n now: opts.deps?.now ?? (() => Date.now()),\n };\n return ensureFreshCredential(base, { config: opts.oauth, store: opts.store, env }, deps);\n}\n\n/**\n * Resolve a fresh credential for `provider` from the store, or `undefined` if the store holds none for it.\n * oauth credentials are auto-refreshed when `opts.oauth` is supplied; api credentials pass through.\n */\nexport async function resolveCredential(\n opts: ResolveCredentialOptions,\n): Promise<ResolvedCredential | undefined> {\n const env = opts.env ?? {};\n const stored = readAuthFile(opts.store, env);\n if (stored === undefined) return undefined;\n\n const path = authFilePath(opts.store, env);\n\n if (stored.type === \"oauth\") {\n return resolveOAuth(stored, path, opts, env);\n }\n\n // api variant: require the stored `provider` to MATCH the requested provider (M42 review MEDIUM-2).\n // The generic SDK cannot infer a provider from a key prefix (that was agent-builder app policy), so a\n // provider-less or mismatched stored key is NOT attributed to the requested provider — otherwise a legacy\n // `{api_key:\"sk-ant-…\"}` file could be POSTed to the wrong vendor's endpoint (cross-vendor key exposure).\n // Fail-closed: credential resolution never attributes a key across providers.\n if (stored.api_key.length === 0) return undefined;\n if (stored.provider !== opts.provider) return undefined;\n return {\n kind: \"api\",\n provider: opts.provider,\n apiKey: stored.api_key,\n source: path,\n inferred: false,\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/internal/runtime/skills/skill-frontmatter.ts","../src/internal/runtime/skills/discover-skills.ts","../src/internal/runtime/system-prompt/escape.ts","../src/internal/runtime/skills/skills-block.ts"],"names":["ConfigurationError","parseSimpleYaml","readWorkspaceDir","safePathJoin","assertNoSymlinkEscape","join","readFile"],"mappings":"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","file":"chunk-7WARNHWW.cjs","sourcesContent":["import { ConfigurationError } from \"../../../errors.js\";\nimport { type FrontmatterValue, parseSimpleYaml } from \"../context/yaml-frontmatter.js\";\n\ntype StringFields = Record<string, string | undefined>;\n\n/** Narrow a FrontmatterValue to string; non-strings + undefined → undefined. */\nfunction asString(v: FrontmatterValue | undefined): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\n\n/** Coerce parser output to legacy string-only shape (skill schema is all-string). */\nfunction toStringFields(raw: Record<string, FrontmatterValue | undefined>): StringFields {\n const out: StringFields = {};\n for (const [k, v] of Object.entries(raw)) out[k] = asString(v);\n return out;\n}\n\n/**\n * Strict skill frontmatter schema (ADR D10).\n *\n * Required: `name`, `description`.\n * Optional: `category`, `dependencies` (comma-separated string in the\n * simple-YAML dialect — parsed to `string[]`).\n *\n * Unknown fields are ignored (forward-compat). Malformed YAML or missing\n * required fields surface as `ConfigurationError` with one of the typed\n * codes below.\n *\n * @internal\n */\nexport interface SkillFrontmatter {\n name: string;\n description: string;\n category?: string;\n dependencies?: string[];\n /**\n * `disable-model-invocation: true` — the model is not told this skill exists.\n *\n * The SDK owns this boundary: a skill reaches the model through exactly one place,\n * `buildSystemPromptContext`, so the declaration is enforceable here. It is a DISCLOSURE rule,\n * not an execution rule — `skills.get(name)` still resolves it, because a caller naming a skill\n * has already made the decision the field exists to keep away from the model.\n */\n disableModelInvocation?: boolean;\n /**\n * `user-invocable: false` — carried, deliberately not enforced here.\n *\n * This SDK has no user-facing invocation surface for skills; there is no slash command. Reading\n * `agent.skills.list()` as \"the user\" would be a guess — a host may call it to build a picker or\n * to introspect, and the two want opposite answers. So the declaration travels to the host that\n * does know, instead of being discarded (the defect) or enforced against an invented boundary.\n */\n userInvocable?: boolean;\n}\n\n/**\n * Parse a SKILL.md file body into validated frontmatter.\n *\n * @throws ConfigurationError(code: \"missing_frontmatter\") — no `---` block at file head.\n * @throws ConfigurationError(code: \"schema_invalid\") — YAML malformed OR required field missing.\n *\n * @internal\n */\nexport function parseSkillFrontmatter(raw: string, fallbackName: string): SkillFrontmatter {\n const parsed = extractAndParseFrontmatter(raw, fallbackName);\n const fields = toStringFields(parsed);\n const name = resolveName(fields, fallbackName);\n ensureRequiredFields(fields, name);\n return buildFrontmatter(fields, parsed, name);\n}\n\n/**\n * SE20 — return a SKILL.md's BODY (everything after the frontmatter block), trimmed.\n * When there is no frontmatter block, the whole file is the body. Reuses the same\n * frontmatter regex as {@link parseSkillFrontmatter} (DRY).\n */\nexport function stripSkillFrontmatter(raw: string): string {\n const match = /^---\\s*\\n[\\s\\S]*?\\n---\\s*\\n/.exec(raw);\n return (match === null ? raw : raw.slice(match[0].length)).trim();\n}\n\nfunction extractAndParseFrontmatter(\n raw: string,\n fallbackName: string,\n): Record<string, FrontmatterValue | undefined> {\n const match = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/.exec(raw);\n if (match === null) {\n throw new ConfigurationError(`Skill ${fallbackName} is missing frontmatter`, {\n code: \"missing_frontmatter\",\n });\n }\n const frontmatter = match[1] ?? \"\";\n // EC-5: guard against syntactically invalid frontmatter so the loader\n // surfaces schema_invalid rather than crashing.\n try {\n return parseSimpleYaml(frontmatter);\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConfigurationError(\n `Skill ${fallbackName} has malformed YAML frontmatter: ${detail}`,\n { code: \"schema_invalid\", cause },\n );\n }\n}\n\nfunction resolveName(fields: StringFields, fallbackName: string): string {\n if (hasContent(fields.name)) return fields.name;\n if (hasContent(fallbackName)) return fallbackName;\n throw new ConfigurationError(\"Skill at unknown path is missing required field: name\", {\n code: \"schema_invalid\",\n });\n}\n\nfunction ensureRequiredFields(fields: StringFields, name: string): void {\n if (!hasContent(fields.description)) {\n throw new ConfigurationError(`Skill ${name} is missing required field: description`, {\n code: \"schema_invalid\",\n });\n }\n}\n\nfunction buildFrontmatter(\n fields: StringFields,\n parsed: Record<string, FrontmatterValue | undefined>,\n name: string,\n): SkillFrontmatter {\n const description = fields.description;\n if (description === undefined) {\n // ensureRequiredFields already threw; this is unreachable but satisfies TS\n throw new ConfigurationError(`Skill ${name} missing description`, { code: \"schema_invalid\" });\n }\n const result: SkillFrontmatter = { name, description };\n if (hasContent(fields.category)) result.category = fields.category;\n const deps = parseDependencies(fields.dependencies);\n if (deps !== undefined) result.dependencies = deps;\n const disable = readAuthorizationFlag(parsed, \"disable-model-invocation\", name);\n if (disable !== undefined) result.disableModelInvocation = disable;\n const invocable = readAuthorizationFlag(parsed, \"user-invocable\", name);\n if (invocable !== undefined) result.userInvocable = invocable;\n return result;\n}\n\n/**\n * Read one boolean authorization flag, refusing a value this parser cannot represent.\n *\n * Unknown frontmatter keys are ignored here for forward compatibility, and that is right for\n * metadata. It is wrong for these two: the simple-YAML dialect coerces only the literals `true`\n * and `false`, so `disable-model-invocation: yes` — a valid YAML boolean — arrives as the STRING\n * `\"yes\"`, compares unequal to `true`, and the skill is disclosed to the model. The author wrote a\n * restriction and got the default.\n *\n * A restriction that fails open is worse than one that is absent, because the author stops looking.\n * So the value is refused and named, and the run stops on the line that caused it.\n */\nfunction readAuthorizationFlag(\n parsed: Record<string, FrontmatterValue | undefined>,\n key: string,\n name: string,\n): boolean | undefined {\n const value = parsed[key];\n if (value === undefined) return undefined;\n if (typeof value === \"boolean\") return value;\n throw new ConfigurationError(\n `Skill ${name}: \"${key}\" must be true or false (got ${JSON.stringify(value)}) — ` +\n `this dialect reads only the literals \\`true\\` and \\`false\\`, and a value it cannot read ` +\n `would leave the skill disclosed`,\n { code: \"schema_invalid\" },\n );\n}\n\nfunction parseDependencies(raw: string | undefined): string[] | undefined {\n if (!hasContent(raw)) return undefined;\n const deps = (raw as string)\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n return deps.length > 0 ? deps : undefined;\n}\n\nfunction hasContent(value: string | undefined): value is string {\n return value !== undefined && value.trim().length > 0;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../../errors.js\";\nimport { assertNoSymlinkEscape, safePathJoin } from \"../../security/path-guard.js\";\nimport { readWorkspaceDir } from \"../config/workspace-dir.js\";\nimport { parseSkillFrontmatter, stripSkillFrontmatter } from \"./skill-frontmatter.js\";\n\n/**\n * A discovered skill's metadata. The skill BODY is never included — only the\n * strict frontmatter fields plus the resolved `source` path.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport interface Skill {\n name: string;\n description: string;\n /** Absolute path to the discovered `SKILL.md`. */\n source: string;\n category?: string;\n dependencies?: string[];\n /** `disable-model-invocation: true` — the model is never told this skill exists. */\n disableModelInvocation?: boolean;\n /** `user-invocable: false` — carried for a host that has a picker; this SDK has no such surface. */\n userInvocable?: boolean;\n}\n\n/**\n * Information passed to `onInvalidSkill` when a `SKILL.md` is present but its\n * frontmatter is malformed (missing required field or invalid YAML).\n *\n * @public\n */\nexport interface InvalidSkillInfo {\n /** The skill directory name (used as the fallback skill name). */\n name: string;\n /** Absolute path to the offending `SKILL.md`. */\n source: string;\n /** Typed reason: `missing_frontmatter` or `schema_invalid`. */\n code: string;\n message: string;\n}\n\n/**\n * Options for {@link discoverSkills}.\n *\n * @public\n */\nexport interface DiscoverSkillsOptions {\n /**\n * Called once per directory that contains a `SKILL.md` with malformed\n * frontmatter. The skill is excluded from the result; discovery continues\n * (strict-frontmatter ADR / EC-5). A directory WITHOUT a `SKILL.md` is NOT a\n * malformed skill and does not trigger this callback.\n *\n * Default: no-op (a library primitive must not write to the consumer's\n * stderr by default).\n */\n onInvalidSkill?: (info: InvalidSkillInfo) => void;\n}\n\n/**\n * Discover `SKILL.md` skills under an arbitrary directory.\n *\n * For each immediate subdirectory `<dir>/<name>/` containing a `SKILL.md`, the\n * file's strict YAML frontmatter is parsed (`name`/`description` required;\n * `category`/`dependencies` optional). Malformed skills are skipped (optionally\n * reported via {@link DiscoverSkillsOptions.onInvalidSkill}); a subdirectory\n * whose realpath escapes `dir` (via symlink) is skipped (symlink-escape guard,\n * reusing `@theokit/sdk/path-safety`).\n *\n * NEVER throws: a missing, unreadable, or non-directory `dir` yields `[]`.\n *\n * Discovery order follows the filesystem `readdir` order (OS-dependent). Sort\n * the result before {@link buildSkillsBlock} if a stable block order matters.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport async function discoverSkills(\n dir: string,\n options?: DiscoverSkillsOptions,\n): Promise<Skill[]> {\n let entries: Awaited<ReturnType<typeof readWorkspaceDir>>;\n try {\n entries = await readWorkspaceDir(dir, \"skills_read_error\", \"skills directory\");\n } catch {\n // never-throw contract: unreadable / not-a-directory → no skills (EC-1)\n return [];\n }\n\n const skills: Skill[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n let skillDir: string;\n try {\n skillDir = safePathJoin(dir, entry.name);\n assertNoSymlinkEscape(skillDir, dir);\n } catch {\n continue;\n }\n const skillPath = join(skillDir, \"SKILL.md\");\n let raw: string;\n try {\n raw = await readFile(skillPath, \"utf8\");\n } catch {\n // no SKILL.md in this subdir → not a skill, not an error (EC-2)\n continue;\n }\n const skill = tryParseSkill(raw, entry.name, skillPath, options);\n if (skill !== undefined) skills.push(skill);\n }\n return skills;\n}\n\nfunction tryParseSkill(\n raw: string,\n fallbackName: string,\n source: string,\n options: DiscoverSkillsOptions | undefined,\n): Skill | undefined {\n try {\n const frontmatter = parseSkillFrontmatter(raw, fallbackName);\n const skill: Skill = {\n name: frontmatter.name,\n description: frontmatter.description,\n source,\n };\n if (frontmatter.category !== undefined) skill.category = frontmatter.category;\n if (frontmatter.dependencies !== undefined) skill.dependencies = frontmatter.dependencies;\n // The authorization flags travel with the record. Dropping them here would reproduce the defect\n // one layer down: the parser would read the declaration and the discovery result would not\n // carry it, which is indistinguishable from never having parsed it.\n if (frontmatter.disableModelInvocation !== undefined) {\n skill.disableModelInvocation = frontmatter.disableModelInvocation;\n }\n if (frontmatter.userInvocable !== undefined) skill.userInvocable = frontmatter.userInvocable;\n return skill;\n } catch (cause) {\n if (cause instanceof ConfigurationError) {\n options?.onInvalidSkill?.({\n name: fallbackName,\n source,\n code: cause.code ?? \"unknown\",\n message: cause.message,\n });\n return undefined;\n }\n throw cause;\n }\n}\n\n/**\n * Read the BODY of a discovered skill — everything after its frontmatter.\n *\n * A thin selector over {@link discoverSkills} rather than a second reader, which is the same\n * relationship `loadSubagentDefinition` has to `discoverSubagents` in the sibling domain: one\n * parser is the point.\n *\n * ## Why this exists rather than a field on `Skill`\n *\n * `Skill` documents that *\"the skill BODY is never included\"*. That is a written contract with no\n * written reason, and widening it on a guess about the reason is not a trade worth making — a\n * catalog you can put in a prompt without carrying every body is the likely intent, and this keeps\n * that shape intact for whoever relied on it.\n *\n * The body was never expensive to obtain: `discoverSkills` already reads each file in full and\n * discards everything but the frontmatter. What was missing was a door that hands it over.\n *\n * ## What it is for\n *\n * Turning a discovered skill into an inline one — `SkillsSettings.inline` requires `instructions`,\n * and without this the only route was to open `source` and split the frontmatter by hand. That is a\n * second implementation of this module's own convention, and it would fail SILENTLY if the format\n * moved: the frontmatter would land inside the instructions and nothing would say so.\n *\n * Reported by the `theocode` session, which needed exactly that to give an operator's\n * `~/.theokit/skills/` to an agent through the SDK's own parser.\n *\n * @param skill - a record returned by {@link discoverSkills}; its `source` is read.\n * @returns the trimmed body. A file that is all frontmatter yields an empty string.\n * @throws if `source` is unreadable — unlike discovery, which skips what it cannot read, a caller\n * naming ONE skill has asked about that skill and an empty string would answer a question it did\n * not ask.\n * @public\n */\nexport async function loadSkillInstructions(skill: Skill): Promise<string> {\n return stripSkillFrontmatter(await readFile(skill.source, \"utf8\"));\n}\n","/**\n * Block-body XML escape (ADR D9 — prompt-injection defence).\n *\n * Order matters: `&` MUST be escaped first so subsequent `<`/`>` replacements\n * do not double-encode the `&` characters they introduce.\n *\n * @internal\n */\nexport const escapeBlockBody = (s: string): string =>\n s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n","import { escapeBlockBody } from \"../system-prompt/escape.js\";\n\n/**\n * Render the `<skills>` system-prompt block from a skill list.\n *\n * Input is the structural subset `{ name, description }` — the skill BODY is\n * NOT in the type, so it cannot leak into the prompt. Both fields are passed\n * through `escapeBlockBody` to neutralise prompt-injection vectors hidden in\n * user-controlled SKILL.md frontmatter (injection-escape ADR).\n *\n * Returns `undefined` for an empty list so the caller can omit the block.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport function buildSkillsBlock(\n skills: ReadonlyArray<{ name: string; description: string }>,\n): string | undefined {\n if (skills.length === 0) return undefined;\n const lines = skills.map(\n (skill) => ` - ${escapeBlockBody(skill.name)}: ${escapeBlockBody(skill.description)}`,\n );\n return `<skills>\\n${lines.join(\"\\n\")}\\n</skills>`;\n}\n"]}