@docyrus/docyrus 0.0.24 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -219,5 +219,38 @@ Inside the interactive agent TUI:
219
219
  ```bash
220
220
  /login # browser or API-key provider chooser
221
221
  /logout # remove saved provider configuration
222
- /architect <brief> # discover tenant data sources and generate local planning artifacts under docs/architect/
222
+ /plan [task] # start a planning-only branch and keep the current plan in .docyrus/plans/
223
+ /end-plan # leave the planning branch, summarize it, and return to the original branch
224
+ /end-architect # alias for leaving an architect planning session
225
+ /plan-policy # show the effective planning-model policy and the resolved planning model
226
+ /architect [brief] # discover tenant data sources and generate local planning artifacts under ./docyrus/architecture/
223
227
  ```
228
+
229
+ When running through `docyrus server`, `/plan` and `/architect` can be sent through `/api/chat` as normal chat input. If clarification is needed, the server emits a synthetic `ask_user` client tool. Frontends using AI SDK `useChat` should render that tool and submit structured answers back with `addToolOutput`.
230
+
231
+ Optional planning-model policy:
232
+
233
+ - Project config: `<project>/.pi/plan-policy.json`
234
+ - Global config: `~/.pi/agent/plan-policy.json`
235
+ - Project config takes precedence over global config
236
+
237
+ Example:
238
+
239
+ ```json
240
+ {
241
+ "models": [
242
+ { "model": "anthropic/claude-sonnet-4", "thinkingLevel": "high" },
243
+ "openai-codex/gpt-5.3-codex"
244
+ ],
245
+ "profiles": {
246
+ "codex-implementation": {
247
+ "match": "openai-codex/gpt-5.3-codex",
248
+ "models": [
249
+ { "model": "anthropic/claude-sonnet-4", "thinkingLevel": "medium" }
250
+ ]
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ `match` is the exact implementation-time `provider/modelId` active when `/plan` starts. The `models` array is an ordered fallback chain. If no configured planning model resolves, `/plan` stays on the current session model.
package/agent-loader.js CHANGED
@@ -1605,6 +1605,188 @@ function getProviderLabel(providerId) {
1605
1605
  return toLabel(providerId);
1606
1606
  }
1607
1607
 
1608
+ // src/agent/authInteractivity.ts
1609
+ function toSelectOptions(values) {
1610
+ return values.map((value) => ({ label: value, value }));
1611
+ }
1612
+ function getProviderFormFields(params) {
1613
+ const provider = params.provider;
1614
+ switch (provider.flow) {
1615
+ case "custom-openai":
1616
+ return [
1617
+ {
1618
+ name: "baseUrl",
1619
+ title: "Custom OpenAI base URL",
1620
+ required: true,
1621
+ component: "text",
1622
+ placeholder: "https://my-proxy.example.com/v1"
1623
+ },
1624
+ {
1625
+ name: "apiKey",
1626
+ title: `${provider.label} API key`,
1627
+ required: true,
1628
+ component: "password"
1629
+ },
1630
+ {
1631
+ name: "modelId",
1632
+ title: "Model ID",
1633
+ required: true,
1634
+ component: "text",
1635
+ placeholder: "gpt-4o"
1636
+ }
1637
+ ];
1638
+ case "azure-openai-responses":
1639
+ return [
1640
+ {
1641
+ name: "apiKey",
1642
+ title: "Azure OpenAI API key",
1643
+ required: true,
1644
+ component: "password"
1645
+ },
1646
+ {
1647
+ name: "configMode",
1648
+ title: "Azure OpenAI configuration",
1649
+ required: true,
1650
+ defaultValue: "base-url",
1651
+ component: "select",
1652
+ options: [
1653
+ { label: "Use a base URL", value: "base-url" },
1654
+ { label: "Use an Azure resource name", value: "resource-name" }
1655
+ ]
1656
+ },
1657
+ {
1658
+ name: "baseUrl",
1659
+ title: "Azure OpenAI base URL",
1660
+ required: true,
1661
+ component: "text",
1662
+ placeholder: "https://my-resource.openai.azure.com/openai/v1",
1663
+ dependsOn: { field: "configMode", equals: "base-url" }
1664
+ },
1665
+ {
1666
+ name: "resourceName",
1667
+ title: "Azure resource name",
1668
+ required: true,
1669
+ component: "text",
1670
+ placeholder: "my-resource",
1671
+ dependsOn: { field: "configMode", equals: "resource-name" }
1672
+ },
1673
+ {
1674
+ name: "modelId",
1675
+ title: "Model ID",
1676
+ required: true,
1677
+ component: params.modelIdsByProvider[provider.id]?.length ? "select" : "text",
1678
+ options: params.modelIdsByProvider[provider.id]?.length ? toSelectOptions(params.modelIdsByProvider[provider.id]) : void 0,
1679
+ defaultValue: params.modelIdsByProvider[provider.id]?.[0] || "gpt-4.1"
1680
+ },
1681
+ {
1682
+ name: "deploymentName",
1683
+ title: "Deployment name",
1684
+ component: "text",
1685
+ placeholder: "gpt-4.1"
1686
+ },
1687
+ {
1688
+ name: "apiVersion",
1689
+ title: "API version",
1690
+ component: "text",
1691
+ defaultValue: "2025-03-01-preview",
1692
+ placeholder: "2025-03-01-preview"
1693
+ }
1694
+ ];
1695
+ case "amazon-bedrock":
1696
+ return [
1697
+ {
1698
+ name: "authMode",
1699
+ title: "Amazon Bedrock authentication",
1700
+ required: true,
1701
+ defaultValue: "profile",
1702
+ component: "select",
1703
+ options: [
1704
+ { label: "Use an AWS profile", value: "profile" },
1705
+ { label: "Paste AWS access keys", value: "access-keys" }
1706
+ ]
1707
+ },
1708
+ {
1709
+ name: "profile",
1710
+ title: "AWS profile name",
1711
+ required: true,
1712
+ defaultValue: "default",
1713
+ component: "text",
1714
+ placeholder: "default",
1715
+ dependsOn: { field: "authMode", equals: "profile" }
1716
+ },
1717
+ {
1718
+ name: "accessKeyId",
1719
+ title: "AWS access key ID",
1720
+ required: true,
1721
+ component: "text",
1722
+ dependsOn: { field: "authMode", equals: "access-keys" }
1723
+ },
1724
+ {
1725
+ name: "secretAccessKey",
1726
+ title: "AWS secret access key",
1727
+ required: true,
1728
+ component: "password",
1729
+ dependsOn: { field: "authMode", equals: "access-keys" }
1730
+ },
1731
+ {
1732
+ name: "sessionToken",
1733
+ title: "AWS session token",
1734
+ component: "password",
1735
+ dependsOn: { field: "authMode", equals: "access-keys" }
1736
+ },
1737
+ {
1738
+ name: "region",
1739
+ title: "AWS region",
1740
+ required: true,
1741
+ defaultValue: "us-east-1",
1742
+ component: "text",
1743
+ placeholder: "us-east-1"
1744
+ },
1745
+ {
1746
+ name: "modelId",
1747
+ title: "Bedrock model ID",
1748
+ required: true,
1749
+ component: params.modelIdsByProvider[provider.id]?.length ? "select" : "text",
1750
+ options: params.modelIdsByProvider[provider.id]?.length ? toSelectOptions(params.modelIdsByProvider[provider.id]) : void 0,
1751
+ defaultValue: params.modelIdsByProvider[provider.id]?.[0] || "anthropic.claude-3-7-sonnet-20250219-v1:0"
1752
+ }
1753
+ ];
1754
+ default:
1755
+ return [
1756
+ {
1757
+ name: "apiKey",
1758
+ title: `${provider.label} API key`,
1759
+ required: true,
1760
+ component: "password"
1761
+ }
1762
+ ];
1763
+ }
1764
+ }
1765
+ async function refreshSessionAfterCredentialChange(params) {
1766
+ params.modelRegistry.refresh();
1767
+ const availableModels = params.modelRegistry.getAvailable();
1768
+ const preferredModel = params.preferredProviderId && params.preferredModelId ? availableModels.find((model) => model.provider === params.preferredProviderId && model.id === params.preferredModelId) : params.preferredProviderId ? availableModels.find((model) => model.provider === params.preferredProviderId) : void 0;
1769
+ const currentModel = params.session.model;
1770
+ const currentModelStillAvailable = currentModel ? availableModels.some((model) => model.provider === currentModel.provider && model.id === currentModel.id) : false;
1771
+ if (currentModel && !currentModelStillAvailable) {
1772
+ if (preferredModel) {
1773
+ await params.session.setModel(preferredModel);
1774
+ return;
1775
+ }
1776
+ if (availableModels[0]) {
1777
+ await params.session.setModel(availableModels[0]);
1778
+ }
1779
+ return;
1780
+ }
1781
+ if (!currentModel && preferredModel) {
1782
+ await params.session.setModel(preferredModel);
1783
+ return;
1784
+ }
1785
+ if (!currentModel && availableModels[0]) {
1786
+ await params.session.setModel(availableModels[0]);
1787
+ }
1788
+ }
1789
+
1608
1790
  // src/agent/interactiveAuth.ts
1609
1791
  function modeAsAny(mode) {
1610
1792
  return mode;
@@ -1674,43 +1856,37 @@ async function showInput(mode, title, placeholder) {
1674
1856
  }
1675
1857
  async function refreshAfterCredentialChange(mode, preferredProviderId, preferredModelId) {
1676
1858
  const modeAny = modeAsAny(mode);
1677
- modeAny.session.modelRegistry.refresh();
1859
+ await refreshSessionAfterCredentialChange({
1860
+ session: modeAny.session,
1861
+ modelRegistry: modeAny.session.modelRegistry,
1862
+ preferredProviderId,
1863
+ preferredModelId
1864
+ });
1678
1865
  await modeAny.updateAvailableProviderCount?.();
1679
- const availableModels = modeAny.session.modelRegistry.getAvailable();
1680
- const preferredModel = preferredProviderId && preferredModelId ? availableModels.find((model) => model.provider === preferredProviderId && model.id === preferredModelId) : preferredProviderId ? availableModels.find((model) => model.provider === preferredProviderId) : void 0;
1681
- const currentModel = modeAny.session.model;
1682
- const currentModelStillAvailable = currentModel ? availableModels.some((model) => model.provider === currentModel.provider && model.id === currentModel.id) : false;
1683
- if (currentModel && !currentModelStillAvailable) {
1684
- if (preferredModel) {
1685
- await modeAny.session.setModel(preferredModel);
1686
- return;
1687
- }
1688
- if (availableModels[0]) {
1689
- await modeAny.session.setModel(availableModels[0]);
1690
- }
1691
- return;
1692
- }
1693
- if (!currentModel && preferredModel) {
1694
- await modeAny.session.setModel(preferredModel);
1695
- return;
1696
- }
1697
- if (!currentModel && availableModels[0]) {
1698
- await modeAny.session.setModel(availableModels[0]);
1699
- }
1866
+ }
1867
+ function getField(provider, modelIdsByProvider, fieldName) {
1868
+ return getProviderFormFields({
1869
+ provider,
1870
+ modelIdsByProvider
1871
+ }).find((field) => field.name === fieldName);
1700
1872
  }
1701
1873
  async function runApiKeyProviderFlow(mode, dependencies, provider) {
1702
1874
  const modeAny = modeAsAny(mode);
1875
+ const field = (name) => getField(provider, dependencies.modelIdsByProvider, name);
1703
1876
  switch (provider.flow) {
1704
1877
  case "custom-openai": {
1705
- const baseUrl = await showInput(mode, "Custom OpenAI base URL", "https://my-proxy.example.com/v1");
1878
+ const baseUrlField = field("baseUrl");
1879
+ const apiKeyField = field("apiKey");
1880
+ const modelIdField = field("modelId");
1881
+ const baseUrl = await showInput(mode, baseUrlField?.title || "Custom OpenAI base URL", baseUrlField?.placeholder);
1706
1882
  if (!baseUrl) {
1707
1883
  return;
1708
1884
  }
1709
- const apiKey = await showInput(mode, "Custom OpenAI API key");
1885
+ const apiKey = await showInput(mode, apiKeyField?.title || "Custom OpenAI API key");
1710
1886
  if (!apiKey) {
1711
1887
  return;
1712
1888
  }
1713
- const modelId = await showInput(mode, "Model ID", "gpt-4o");
1889
+ const modelId = await showInput(mode, modelIdField?.title || "Model ID", modelIdField?.placeholder);
1714
1890
  if (!modelId) {
1715
1891
  return;
1716
1892
  }
@@ -1725,22 +1901,33 @@ async function runApiKeyProviderFlow(mode, dependencies, provider) {
1725
1901
  return;
1726
1902
  }
1727
1903
  case "azure-openai-responses": {
1728
- const apiKey = await showInput(mode, "Azure OpenAI API key");
1904
+ const apiKeyField = field("apiKey");
1905
+ const configModeField = field("configMode");
1906
+ const baseUrlField = field("baseUrl");
1907
+ const resourceNameField = field("resourceName");
1908
+ const modelIdField = field("modelId");
1909
+ const deploymentNameField = field("deploymentName");
1910
+ const apiVersionField = field("apiVersion");
1911
+ const apiKey = await showInput(mode, apiKeyField?.title || "Azure OpenAI API key");
1729
1912
  if (!apiKey) {
1730
1913
  return;
1731
1914
  }
1732
- const configMode = await showSelection(mode, "Azure OpenAI configuration", [
1733
- { value: "base-url", label: "Use a base URL" },
1734
- { value: "resource-name", label: "Use an Azure resource name" }
1735
- ]);
1915
+ const configMode = await showSelection(
1916
+ mode,
1917
+ configModeField?.title || "Azure OpenAI configuration",
1918
+ (configModeField?.options ?? []).map((option) => ({
1919
+ value: option.value,
1920
+ label: option.label
1921
+ }))
1922
+ );
1736
1923
  if (!configMode) {
1737
1924
  return;
1738
1925
  }
1739
1926
  let baseUrl;
1740
1927
  if (configMode === "base-url") {
1741
- baseUrl = await showInput(mode, "Azure OpenAI base URL", "https://my-resource.openai.azure.com/openai/v1");
1928
+ baseUrl = await showInput(mode, baseUrlField?.title || "Azure OpenAI base URL", baseUrlField?.placeholder);
1742
1929
  } else {
1743
- const resourceName = await showInput(mode, "Azure resource name", "my-resource");
1930
+ const resourceName = await showInput(mode, resourceNameField?.title || "Azure resource name", resourceNameField?.placeholder);
1744
1931
  if (resourceName) {
1745
1932
  baseUrl = `https://${resourceName}.openai.azure.com/openai/v1`;
1746
1933
  }
@@ -1748,12 +1935,15 @@ async function runApiKeyProviderFlow(mode, dependencies, provider) {
1748
1935
  if (!baseUrl) {
1749
1936
  return;
1750
1937
  }
1751
- const modelId = await showInput(mode, "Model ID", dependencies.modelIdsByProvider["azure-openai-responses"]?.[0] || "gpt-4.1");
1938
+ const modelId = modelIdField?.options?.length ? await showSelection(mode, modelIdField.title, modelIdField.options.map((option) => ({
1939
+ value: option.value,
1940
+ label: option.label
1941
+ }))) : await showInput(mode, modelIdField?.title || "Model ID", modelIdField?.placeholder || modelIdField?.defaultValue);
1752
1942
  if (!modelId) {
1753
1943
  return;
1754
1944
  }
1755
- const deploymentName = await showInput(mode, "Deployment name (optional)", modelId);
1756
- const apiVersion = await showInput(mode, "API version (optional)", "2025-03-01-preview");
1945
+ const deploymentName = await showInput(mode, deploymentNameField?.title || "Deployment name", deploymentNameField?.placeholder || modelId);
1946
+ const apiVersion = await showInput(mode, apiVersionField?.title || "API version", apiVersionField?.placeholder || apiVersionField?.defaultValue);
1757
1947
  const builtInModelIds = new Set(dependencies.modelIdsByProvider["azure-openai-responses"] || []);
1758
1948
  await saveAzureConfig({
1759
1949
  ...dependencies,
@@ -1769,10 +1959,21 @@ async function runApiKeyProviderFlow(mode, dependencies, provider) {
1769
1959
  return;
1770
1960
  }
1771
1961
  case "amazon-bedrock": {
1772
- const authMode = await showSelection(mode, "Amazon Bedrock authentication", [
1773
- { value: "profile", label: "Use an AWS profile" },
1774
- { value: "access-keys", label: "Paste AWS access keys" }
1775
- ]);
1962
+ const authModeField = field("authMode");
1963
+ const profileField = field("profile");
1964
+ const accessKeyIdField = field("accessKeyId");
1965
+ const secretAccessKeyField = field("secretAccessKey");
1966
+ const sessionTokenField = field("sessionToken");
1967
+ const regionField = field("region");
1968
+ const modelIdField = field("modelId");
1969
+ const authMode = await showSelection(
1970
+ mode,
1971
+ authModeField?.title || "Amazon Bedrock authentication",
1972
+ (authModeField?.options ?? []).map((option) => ({
1973
+ value: option.value,
1974
+ label: option.label
1975
+ }))
1976
+ );
1776
1977
  if (!authMode) {
1777
1978
  return;
1778
1979
  }
@@ -1781,26 +1982,29 @@ async function runApiKeyProviderFlow(mode, dependencies, provider) {
1781
1982
  let secretAccessKey;
1782
1983
  let sessionToken;
1783
1984
  if (authMode === "profile") {
1784
- profile = await showInput(mode, "AWS profile name", "default");
1985
+ profile = await showInput(mode, profileField?.title || "AWS profile name", profileField?.placeholder || profileField?.defaultValue);
1785
1986
  if (!profile) {
1786
1987
  return;
1787
1988
  }
1788
1989
  } else {
1789
- accessKeyId = await showInput(mode, "AWS access key ID");
1990
+ accessKeyId = await showInput(mode, accessKeyIdField?.title || "AWS access key ID");
1790
1991
  if (!accessKeyId) {
1791
1992
  return;
1792
1993
  }
1793
- secretAccessKey = await showInput(mode, "AWS secret access key");
1994
+ secretAccessKey = await showInput(mode, secretAccessKeyField?.title || "AWS secret access key");
1794
1995
  if (!secretAccessKey) {
1795
1996
  return;
1796
1997
  }
1797
- sessionToken = await showInput(mode, "AWS session token (optional)");
1998
+ sessionToken = await showInput(mode, sessionTokenField?.title || "AWS session token");
1798
1999
  }
1799
- const region = await showInput(mode, "AWS region", "us-east-1");
2000
+ const region = await showInput(mode, regionField?.title || "AWS region", regionField?.placeholder || regionField?.defaultValue);
1800
2001
  if (!region) {
1801
2002
  return;
1802
2003
  }
1803
- const modelId = await showInput(mode, "Bedrock model ID", dependencies.modelIdsByProvider["amazon-bedrock"]?.[0] || "anthropic.claude-3-7-sonnet-20250219-v1:0");
2004
+ const modelId = modelIdField?.options?.length ? await showSelection(mode, modelIdField.title, modelIdField.options.map((option) => ({
2005
+ value: option.value,
2006
+ label: option.label
2007
+ }))) : await showInput(mode, modelIdField?.title || "Bedrock model ID", modelIdField?.placeholder || modelIdField?.defaultValue);
1804
2008
  if (!modelId) {
1805
2009
  return;
1806
2010
  }